/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.policy; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.provider.Settings; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.util.Log; import android.view.View; import android.widget.TextView; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.cdma.EriInfo; import com.android.internal.util.AsyncChannel; import com.android.systemui.DemoMode; import com.android.systemui.R; import com.android.systemui.statusbar.phone.StatusBarHeaderView; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** Platform implementation of the network controller. **/ public class NetworkControllerImpl extends BroadcastReceiver implements NetworkController, DemoMode { // debug static final String TAG = "StatusBar.NetworkController"; static final boolean DEBUG = false; static final boolean CHATTY = false; // additional diagnostics, but not logspew // telephony boolean mHspaDataDistinguishable; final TelephonyManager mPhone; boolean mDataConnected; IccCardConstants.State mSimState = IccCardConstants.State.READY; int mPhoneState = TelephonyManager.CALL_STATE_IDLE; int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN; int mDataState = TelephonyManager.DATA_DISCONNECTED; int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE; ServiceState mServiceState; SignalStrength mSignalStrength; int[] mDataIconList = TelephonyIcons.DATA_G[0]; String mNetworkName; String mNetworkNameDefault; String mNetworkNameSeparator; int mPhoneSignalIconId; int mQSPhoneSignalIconId; int mDataDirectionIconId; // data + data direction on phones int mDataSignalIconId; int mDataTypeIconId; int mQSDataTypeIconId; int mAirplaneIconId; boolean mDataActive; boolean mNoSim; int mLastSignalLevel; boolean mShowPhoneRSSIForData = false; boolean mShowAtLeastThreeGees = false; boolean mAlwaysShowCdmaRssi = false; String mContentDescriptionPhoneSignal; String mContentDescriptionWifi; String mContentDescriptionWimax; String mContentDescriptionCombinedSignal; String mContentDescriptionDataType; // wifi final WifiManager mWifiManager; AsyncChannel mWifiChannel; boolean mWifiEnabled, mWifiConnected; int mWifiRssi, mWifiLevel; String mWifiSsid; int mWifiIconId = 0; int mQSWifiIconId = 0; int mWifiActivity = WifiManager.DATA_ACTIVITY_NONE; // bluetooth private boolean mBluetoothTethered = false; private int mBluetoothTetherIconId = com.android.internal.R.drawable.stat_sys_tether_bluetooth; //wimax private boolean mWimaxSupported = false; private boolean mIsWimaxEnabled = false; private boolean mWimaxConnected = false; private boolean mWimaxIdle = false; private int mWimaxIconId = 0; private int mWimaxSignal = 0; private int mWimaxState = 0; private int mWimaxExtraState = 0; // data connectivity (regardless of state, can we access the internet?) // state of inet connection - 0 not connected, 100 connected private boolean mConnected = false; private int mConnectedNetworkType = ConnectivityManager.TYPE_NONE; private String mConnectedNetworkTypeName; private int mLastConnectedNetworkType = ConnectivityManager.TYPE_NONE; private int mInetCondition = 0; private int mLastInetCondition = 0; private static final int INET_CONDITION_THRESHOLD = 50; private boolean mAirplaneMode = false; private boolean mLastAirplaneMode = true; private Locale mLocale = null; private Locale mLastLocale = null; // our ui Context mContext; ArrayList mCombinedLabelViews = new ArrayList(); ArrayList mMobileLabelViews = new ArrayList(); ArrayList mWifiLabelViews = new ArrayList(); ArrayList mEmergencyViews = new ArrayList<>(); ArrayList mSignalClusters = new ArrayList(); ArrayList mSignalsChangedCallbacks = new ArrayList(); int mLastPhoneSignalIconId = -1; int mLastDataDirectionIconId = -1; int mLastWifiIconId = -1; int mLastWimaxIconId = -1; int mLastCombinedSignalIconId = -1; int mLastDataTypeIconId = -1; String mLastCombinedLabel = ""; private boolean mHasMobileDataFeature; boolean mDataAndWifiStacked = false; public interface SignalCluster { void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription); void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon, String contentDescription, String typeContentDescription, boolean roaming, boolean isTypeIconWide); void setIsAirplaneMode(boolean is, int airplaneIcon); } private final WifiAccessPointController mAccessPoints; private final MobileDataController mMobileDataController; /** * Construct this controller object and register for updates. */ public NetworkControllerImpl(Context context) { mContext = context; final Resources res = context.getResources(); ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); mHasMobileDataFeature = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); mShowPhoneRSSIForData = res.getBoolean(R.bool.config_showPhoneRSSIForData); mShowAtLeastThreeGees = res.getBoolean(R.bool.config_showMin3G); mAlwaysShowCdmaRssi = res.getBoolean( com.android.internal.R.bool.config_alwaysUseCdmaRssi); // set up the default wifi icon, used when no radios have ever appeared updateWifiIcons(); updateWimaxIcons(); // telephony mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); mPhone.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS | PhoneStateListener.LISTEN_CALL_STATE | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE | PhoneStateListener.LISTEN_DATA_ACTIVITY); mHspaDataDistinguishable = mContext.getResources().getBoolean( R.bool.config_hspa_data_distinguishable); mNetworkNameSeparator = mContext.getString(R.string.status_bar_network_name_separator); mNetworkNameDefault = mContext.getString( com.android.internal.R.string.lockscreen_carrier_default); mNetworkName = mNetworkNameDefault; // wifi mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); Handler handler = new WifiHandler(); mWifiChannel = new AsyncChannel(); Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger(); if (wifiMessenger != null) { mWifiChannel.connect(mContext, handler, wifiMessenger); } // broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(WifiManager.RSSI_CHANGED_ACTION); filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE); filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); mWimaxSupported = mContext.getResources().getBoolean( com.android.internal.R.bool.config_wimaxEnabled); if(mWimaxSupported) { filter.addAction(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION); filter.addAction(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION); filter.addAction(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION); } context.registerReceiver(this, filter); // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it updateAirplaneMode(); mLastLocale = mContext.getResources().getConfiguration().locale; mAccessPoints = new WifiAccessPointController(mContext); mMobileDataController = new MobileDataController(mContext); mMobileDataController.setCallback(new MobileDataController.Callback() { @Override public void onMobileDataEnabled(boolean enabled) { notifyMobileDataEnabled(enabled); } }); } private void notifyMobileDataEnabled(boolean enabled) { for (NetworkSignalChangedCallback cb : mSignalsChangedCallbacks) { cb.onMobileDataEnabled(enabled); } } public boolean hasMobileDataFeature() { return mHasMobileDataFeature; } public boolean hasVoiceCallingFeature() { return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; } public boolean isEmergencyOnly() { return (mServiceState != null && mServiceState.isEmergencyOnly()); } public void addCombinedLabelView(TextView v) { mCombinedLabelViews.add(v); } public void addMobileLabelView(TextView v) { mMobileLabelViews.add(v); } public void addWifiLabelView(TextView v) { mWifiLabelViews.add(v); } public void addEmergencyLabelView(StatusBarHeaderView v) { mEmergencyViews.add(v); } public void addSignalCluster(SignalCluster cluster) { mSignalClusters.add(cluster); refreshSignalCluster(cluster); } public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) { mSignalsChangedCallbacks.add(cb); notifySignalsChangedCallbacks(cb); } public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) { mSignalsChangedCallbacks.remove(cb); } @Override public void addAccessPointCallback(AccessPointCallback callback) { mAccessPoints.addCallback(callback); } @Override public void removeAccessPointCallback(AccessPointCallback callback) { mAccessPoints.removeCallback(callback); } @Override public void scanForAccessPoints() { mAccessPoints.scan(); } @Override public void connect(AccessPoint ap) { mAccessPoints.connect(ap); } @Override public void setWifiEnabled(final boolean enabled) { new AsyncTask() { @Override protected Void doInBackground(Void... args) { // Disable tethering if enabling Wifi final int wifiApState = mWifiManager.getWifiApState(); if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) { mWifiManager.setWifiApEnabled(null, false); } mWifiManager.setWifiEnabled(enabled); return null; } }.execute(); } @Override public DataUsageInfo getDataUsageInfo() { final DataUsageInfo info = mMobileDataController.getDataUsageInfo(); if (info != null) { info.carrier = mNetworkName; } return info; } @Override public boolean isMobileDataSupported() { return mMobileDataController.isMobileDataSupported(); } @Override public boolean isMobileDataEnabled() { return mMobileDataController.isMobileDataEnabled(); } @Override public void setMobileDataEnabled(boolean enabled) { mMobileDataController.setMobileDataEnabled(enabled); } private boolean isTypeIconWide(int iconId) { return TelephonyIcons.ICON_LTE == iconId || TelephonyIcons.ICON_1X == iconId || TelephonyIcons.ICON_3G == iconId || TelephonyIcons.ICON_4G == iconId; } private boolean isQsTypeIconWide(int iconId) { return TelephonyIcons.QS_ICON_LTE == iconId || TelephonyIcons.QS_ICON_1X == iconId || TelephonyIcons.QS_ICON_3G == iconId || TelephonyIcons.QS_ICON_4G == iconId; } public void refreshSignalCluster(SignalCluster cluster) { if (mDemoMode) return; cluster.setWifiIndicators( // only show wifi in the cluster if connected or if wifi-only mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature), mWifiIconId, mContentDescriptionWifi); if (mIsWimaxEnabled && mWimaxConnected) { // wimax is special cluster.setMobileDataIndicators( true, mAlwaysShowCdmaRssi ? mPhoneSignalIconId : mWimaxIconId, mDataTypeIconId, mContentDescriptionWimax, mContentDescriptionDataType, mDataTypeIconId == TelephonyIcons.ROAMING_ICON, false /* isTypeIconWide */ ); } else { // normal mobile data cluster.setMobileDataIndicators( mHasMobileDataFeature, mShowPhoneRSSIForData ? mPhoneSignalIconId : mDataSignalIconId, mDataTypeIconId, mContentDescriptionPhoneSignal, mContentDescriptionDataType, mDataTypeIconId == TelephonyIcons.ROAMING_ICON, isTypeIconWide(mDataTypeIconId)); } cluster.setIsAirplaneMode(mAirplaneMode, mAirplaneIconId); } void notifySignalsChangedCallbacks(NetworkSignalChangedCallback cb) { // only show wifi in the cluster if connected or if wifi-only boolean wifiEnabled = mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature); String wifiDesc = wifiEnabled ? mWifiSsid : null; boolean wifiIn = wifiEnabled && mWifiSsid != null && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT || mWifiActivity == WifiManager.DATA_ACTIVITY_IN); boolean wifiOut = wifiEnabled && mWifiSsid != null && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT || mWifiActivity == WifiManager.DATA_ACTIVITY_OUT); cb.onWifiSignalChanged(mWifiEnabled, mWifiConnected, mQSWifiIconId, wifiIn, wifiOut, mContentDescriptionWifi, wifiDesc); boolean mobileIn = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT || mDataActivity == TelephonyManager.DATA_ACTIVITY_IN); boolean mobileOut = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT || mDataActivity == TelephonyManager.DATA_ACTIVITY_OUT); if (isEmergencyOnly()) { cb.onMobileDataSignalChanged(false, mQSPhoneSignalIconId, mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut, mContentDescriptionDataType, null, mNoSim, isQsTypeIconWide(mQSDataTypeIconId)); } else { if (mIsWimaxEnabled && mWimaxConnected) { // Wimax is special cb.onMobileDataSignalChanged(true, mQSPhoneSignalIconId, mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut, mContentDescriptionDataType, mNetworkName, mNoSim, isQsTypeIconWide(mQSDataTypeIconId)); } else { // Normal mobile data cb.onMobileDataSignalChanged(mHasMobileDataFeature, mQSPhoneSignalIconId, mContentDescriptionPhoneSignal, mQSDataTypeIconId, mobileIn, mobileOut, mContentDescriptionDataType, mNetworkName, mNoSim, isQsTypeIconWide(mQSDataTypeIconId)); } } cb.onAirplaneModeChanged(mAirplaneMode); } public void setStackedMode(boolean stacked) { mDataAndWifiStacked = true; } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(WifiManager.RSSI_CHANGED_ACTION) || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION) || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { updateWifiState(intent); refreshViews(); } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { updateSimState(intent); updateDataIcon(); refreshViews(); } else if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) { updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false), intent.getStringExtra(TelephonyIntents.EXTRA_SPN), intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false), intent.getStringExtra(TelephonyIntents.EXTRA_PLMN)); refreshViews(); } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) || action.equals(ConnectivityManager.INET_CONDITION_ACTION)) { updateConnectivity(intent); refreshViews(); } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { refreshLocale(); refreshViews(); } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { refreshLocale(); updateAirplaneMode(); refreshViews(); } else if (action.equals(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION) || action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION) || action.equals(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION)) { updateWimaxState(intent); refreshViews(); } } // ===== Telephony ============================================================== PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onSignalStrengthsChanged(SignalStrength signalStrength) { if (DEBUG) { Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength + ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel()))); } mSignalStrength = signalStrength; updateTelephonySignalStrength(); refreshViews(); } @Override public void onServiceStateChanged(ServiceState state) { if (DEBUG) { Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState() + " dataState=" + state.getDataRegState()); } mServiceState = state; updateTelephonySignalStrength(); updateDataNetType(); updateDataIcon(); refreshViews(); } @Override public void onCallStateChanged(int state, String incomingNumber) { if (DEBUG) { Log.d(TAG, "onCallStateChanged state=" + state); } // In cdma, if a voice call is made, RSSI should switch to 1x. if (isCdma()) { updateTelephonySignalStrength(); refreshViews(); } } @Override public void onDataConnectionStateChanged(int state, int networkType) { if (DEBUG) { Log.d(TAG, "onDataConnectionStateChanged: state=" + state + " type=" + networkType); } mDataState = state; mDataNetType = networkType; updateDataNetType(); updateDataIcon(); refreshViews(); } @Override public void onDataActivity(int direction) { if (DEBUG) { Log.d(TAG, "onDataActivity: direction=" + direction); } mDataActivity = direction; updateDataIcon(); refreshViews(); } }; private final void updateSimState(Intent intent) { String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { mSimState = IccCardConstants.State.ABSENT; } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) { mSimState = IccCardConstants.State.READY; } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { final String lockedReason = intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { mSimState = IccCardConstants.State.PIN_REQUIRED; } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { mSimState = IccCardConstants.State.PUK_REQUIRED; } else { mSimState = IccCardConstants.State.NETWORK_LOCKED; } } else { mSimState = IccCardConstants.State.UNKNOWN; } if (DEBUG) Log.d(TAG, "updateSimState: mSimState=" + mSimState); } private boolean isCdma() { return (mSignalStrength != null) && !mSignalStrength.isGsm(); } private boolean hasService() { boolean retVal; if (mServiceState != null) { // Consider the device to be in service if either voice or data service is available. // Some SIM cards are marketed as data-only and do not support voice service, and on // these SIM cards, we want to show signal bars for data service as well as the "no // service" or "emergency calls only" text that indicates that voice is not available. switch(mServiceState.getVoiceRegState()) { case ServiceState.STATE_POWER_OFF: retVal = false; break; case ServiceState.STATE_OUT_OF_SERVICE: case ServiceState.STATE_EMERGENCY_ONLY: retVal = mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE; break; default: retVal = true; } } else { retVal = false; } if (DEBUG) Log.d(TAG, "hasService: mServiceState=" + mServiceState + " retVal=" + retVal); return retVal; } private void updateAirplaneMode() { mAirplaneMode = (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1); } private void refreshLocale() { mLocale = mContext.getResources().getConfiguration().locale; } private final void updateTelephonySignalStrength() { if (DEBUG) { Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService() + " ss=" + mSignalStrength); } if (!hasService()) { if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: !hasService()"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; mContentDescriptionPhoneSignal = mContext.getString( AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]); } else { if (mSignalStrength == null) { if (CHATTY) Log.d(TAG, "updateTelephonySignalStrength: mSignalStrength == null"); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mQSPhoneSignalIconId = R.drawable.ic_qs_signal_no_signal; mDataSignalIconId = R.drawable.stat_sys_signal_null; mContentDescriptionPhoneSignal = mContext.getString( AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0]); } else { int iconLevel; int[] iconList; if (isCdma() && mAlwaysShowCdmaRssi) { mLastSignalLevel = iconLevel = mSignalStrength.getCdmaLevel(); if (DEBUG) { Log.d(TAG, "updateTelephonySignalStrength:" + " mAlwaysShowCdmaRssi=" + mAlwaysShowCdmaRssi + " set to cdmaLevel=" + mSignalStrength.getCdmaLevel() + " instead of level=" + mSignalStrength.getLevel()); } } else { mLastSignalLevel = iconLevel = mSignalStrength.getLevel(); } if (isRoaming()) { iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH_ROAMING[mInetCondition]; } else { iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition]; } mPhoneSignalIconId = iconList[iconLevel]; mQSPhoneSignalIconId = TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[mInetCondition][iconLevel]; mContentDescriptionPhoneSignal = mContext.getString( AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[iconLevel]); mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel]; if (DEBUG) Log.d(TAG, "updateTelephonySignalStrength: iconLevel=" + iconLevel); } } } private int inetConditionForNetwork(int networkType) { return (mInetCondition == 1 && mConnectedNetworkType == networkType) ? 1 : 0; } private final void updateDataNetType() { int inetCondition; mDataTypeIconId = mQSDataTypeIconId = 0; if (mIsWimaxEnabled && mWimaxConnected) { // wimax is a special 4g network not handled by telephony inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIMAX); mDataIconList = TelephonyIcons.DATA_4G[inetCondition]; mDataTypeIconId = R.drawable.stat_sys_data_fully_connected_4g; mQSDataTypeIconId = TelephonyIcons.QS_DATA_4G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_4g); } else { inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_MOBILE); final boolean showDataTypeIcon = (inetCondition > 0); switch (mDataNetType) { case TelephonyManager.NETWORK_TYPE_UNKNOWN: if (!mShowAtLeastThreeGees) { mDataIconList = TelephonyIcons.DATA_G[inetCondition]; mContentDescriptionDataType = ""; break; } else { // fall through } case TelephonyManager.NETWORK_TYPE_EDGE: if (!mShowAtLeastThreeGees) { mDataIconList = TelephonyIcons.DATA_E[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_e : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_E[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_edge); break; } else { // fall through } case TelephonyManager.NETWORK_TYPE_UMTS: mDataIconList = TelephonyIcons.DATA_3G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_3g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_3g); break; case TelephonyManager.NETWORK_TYPE_HSDPA: case TelephonyManager.NETWORK_TYPE_HSUPA: case TelephonyManager.NETWORK_TYPE_HSPA: case TelephonyManager.NETWORK_TYPE_HSPAP: if (mHspaDataDistinguishable) { mDataIconList = TelephonyIcons.DATA_H[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_h : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_H[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_3_5g); } else { mDataIconList = TelephonyIcons.DATA_3G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_3g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_3g); } break; case TelephonyManager.NETWORK_TYPE_CDMA: if (!mShowAtLeastThreeGees) { // display 1xRTT for IS95A/B mDataIconList = TelephonyIcons.DATA_1X[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_1x : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_1X[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_cdma); break; } else { // fall through } case TelephonyManager.NETWORK_TYPE_1xRTT: if (!mShowAtLeastThreeGees) { mDataIconList = TelephonyIcons.DATA_1X[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_1x : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_1X[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_cdma); break; } else { // fall through } case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through case TelephonyManager.NETWORK_TYPE_EVDO_A: case TelephonyManager.NETWORK_TYPE_EVDO_B: case TelephonyManager.NETWORK_TYPE_EHRPD: mDataIconList = TelephonyIcons.DATA_3G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_3g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_3g); break; case TelephonyManager.NETWORK_TYPE_LTE: boolean show4GforLTE = mContext.getResources().getBoolean(R.bool.config_show4GForLTE); if (show4GforLTE) { mDataIconList = TelephonyIcons.DATA_4G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_4g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_4G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_4g); } else { mDataIconList = TelephonyIcons.DATA_LTE[inetCondition]; mDataTypeIconId = showDataTypeIcon ? TelephonyIcons.ICON_LTE : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_LTE[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_lte); } break; default: if (!mShowAtLeastThreeGees) { mDataIconList = TelephonyIcons.DATA_G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_gprs); } else { mDataIconList = TelephonyIcons.DATA_3G[inetCondition]; mDataTypeIconId = showDataTypeIcon ? R.drawable.stat_sys_data_fully_connected_3g : 0; mQSDataTypeIconId = TelephonyIcons.QS_DATA_3G[inetCondition]; mContentDescriptionDataType = mContext.getString( R.string.accessibility_data_connection_3g); } break; } } if (isRoaming()) { mDataTypeIconId = TelephonyIcons.ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } boolean isCdmaEri() { if (mServiceState != null) { final int iconIndex = mServiceState.getCdmaEriIconIndex(); if (iconIndex != EriInfo.ROAMING_INDICATOR_OFF) { final int iconMode = mServiceState.getCdmaEriIconMode(); if (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH) { return true; } } } return false; } private boolean isRoaming() { if (isCdma()) { return isCdmaEri(); } else { return mServiceState != null && mServiceState.getRoaming(); } } private final void updateDataIcon() { int iconId; boolean visible = true; if (!isCdma()) { // GSM case, we have to check also the sim state if (mSimState == IccCardConstants.State.READY || mSimState == IccCardConstants.State.UNKNOWN) { mNoSim = false; if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { switch (mDataActivity) { case TelephonyManager.DATA_ACTIVITY_IN: iconId = mDataIconList[1]; break; case TelephonyManager.DATA_ACTIVITY_OUT: iconId = mDataIconList[2]; break; case TelephonyManager.DATA_ACTIVITY_INOUT: iconId = mDataIconList[3]; break; default: iconId = mDataIconList[0]; break; } mDataDirectionIconId = iconId; } else { iconId = 0; visible = false; } } else { iconId = 0; mNoSim = true; visible = false; // no SIM? no data } } else { // CDMA case, mDataActivity can be also DATA_ACTIVITY_DORMANT if (hasService() && mDataState == TelephonyManager.DATA_CONNECTED) { switch (mDataActivity) { case TelephonyManager.DATA_ACTIVITY_IN: iconId = mDataIconList[1]; break; case TelephonyManager.DATA_ACTIVITY_OUT: iconId = mDataIconList[2]; break; case TelephonyManager.DATA_ACTIVITY_INOUT: iconId = mDataIconList[3]; break; case TelephonyManager.DATA_ACTIVITY_DORMANT: default: iconId = mDataIconList[0]; break; } } else { iconId = 0; visible = false; } } mDataDirectionIconId = iconId; mDataConnected = visible; } void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) { if (false) { Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn + " showPlmn=" + showPlmn + " plmn=" + plmn); } StringBuilder str = new StringBuilder(); boolean something = false; if (showPlmn && plmn != null) { str.append(plmn); something = true; } if (showSpn && spn != null) { if (something) { str.append(mNetworkNameSeparator); } str.append(spn); something = true; } if (something) { mNetworkName = str.toString(); } else { mNetworkName = mNetworkNameDefault; } } // ===== Wifi =================================================================== class WifiHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { mWifiChannel.sendMessage(Message.obtain(this, AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)); } else { Log.e(TAG, "Failed to connect to wifi"); } break; case WifiManager.DATA_ACTIVITY_NOTIFICATION: if (msg.arg1 != mWifiActivity) { mWifiActivity = msg.arg1; refreshViews(); } break; default: //Ignore break; } } } private void updateWifiState(Intent intent) { final String action = intent.getAction(); if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { mWifiEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED; } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { final NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); boolean wasConnected = mWifiConnected; mWifiConnected = networkInfo != null && networkInfo.isConnected(); // If Connected grab the signal strength and ssid if (mWifiConnected) { // try getting it out of the intent first WifiInfo info = (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO); if (info == null) { info = mWifiManager.getConnectionInfo(); } if (info != null) { mWifiSsid = huntForSsid(info); } else { mWifiSsid = null; } } else if (!mWifiConnected) { mWifiSsid = null; } } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) { mWifiRssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200); mWifiLevel = WifiManager.calculateSignalLevel( mWifiRssi, WifiIcons.WIFI_LEVEL_COUNT); } updateWifiIcons(); } private void updateWifiIcons() { int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIFI); if (mWifiConnected) { mWifiIconId = WifiIcons.WIFI_SIGNAL_STRENGTH[inetCondition][mWifiLevel]; mQSWifiIconId = WifiIcons.QS_WIFI_SIGNAL_STRENGTH[inetCondition][mWifiLevel]; mContentDescriptionWifi = mContext.getString( AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH[mWifiLevel]); } else { if (mDataAndWifiStacked) { mWifiIconId = 0; mQSWifiIconId = 0; } else { mWifiIconId = mWifiEnabled ? R.drawable.stat_sys_wifi_signal_null : 0; mQSWifiIconId = mWifiEnabled ? R.drawable.ic_qs_wifi_no_network : 0; } mContentDescriptionWifi = mContext.getString(R.string.accessibility_no_wifi); } } private String huntForSsid(WifiInfo info) { String ssid = info.getSSID(); if (ssid != null) { return ssid; } // OK, it's not in the connectionInfo; we have to go hunting for it List networks = mWifiManager.getConfiguredNetworks(); for (WifiConfiguration net : networks) { if (net.networkId == info.getNetworkId()) { return net.SSID; } } return null; } // ===== Wimax =================================================================== private final void updateWimaxState(Intent intent) { final String action = intent.getAction(); boolean wasConnected = mWimaxConnected; if (action.equals(WimaxManagerConstants.NET_4G_STATE_CHANGED_ACTION)) { int wimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_4G_STATE, WimaxManagerConstants.NET_4G_STATE_UNKNOWN); mIsWimaxEnabled = (wimaxStatus == WimaxManagerConstants.NET_4G_STATE_ENABLED); } else if (action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION)) { mWimaxSignal = intent.getIntExtra(WimaxManagerConstants.EXTRA_NEW_SIGNAL_LEVEL, 0); } else if (action.equals(WimaxManagerConstants.WIMAX_NETWORK_STATE_CHANGED_ACTION)) { mWimaxState = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATE, WimaxManagerConstants.NET_4G_STATE_UNKNOWN); mWimaxExtraState = intent.getIntExtra( WimaxManagerConstants.EXTRA_WIMAX_STATE_DETAIL, WimaxManagerConstants.NET_4G_STATE_UNKNOWN); mWimaxConnected = (mWimaxState == WimaxManagerConstants.WIMAX_STATE_CONNECTED); mWimaxIdle = (mWimaxExtraState == WimaxManagerConstants.WIMAX_IDLE); } updateDataNetType(); updateWimaxIcons(); } private void updateWimaxIcons() { if (mIsWimaxEnabled) { if (mWimaxConnected) { int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_WIMAX); if (mWimaxIdle) mWimaxIconId = WimaxIcons.WIMAX_IDLE; else mWimaxIconId = WimaxIcons.WIMAX_SIGNAL_STRENGTH[inetCondition][mWimaxSignal]; mContentDescriptionWimax = mContext.getString( AccessibilityContentDescriptions.WIMAX_CONNECTION_STRENGTH[mWimaxSignal]); } else { mWimaxIconId = WimaxIcons.WIMAX_DISCONNECTED; mContentDescriptionWimax = mContext.getString(R.string.accessibility_no_wimax); } } else { mWimaxIconId = 0; } } // ===== Full or limited Internet connectivity ================================== private void updateConnectivity(Intent intent) { if (CHATTY) { Log.d(TAG, "updateConnectivity: intent=" + intent); } final ConnectivityManager connManager = (ConnectivityManager) mContext .getSystemService(Context.CONNECTIVITY_SERVICE); final NetworkInfo info = connManager.getActiveNetworkInfo(); // Are we connected at all, by any interface? mConnected = info != null && info.isConnected(); if (mConnected) { mConnectedNetworkType = info.getType(); mConnectedNetworkTypeName = info.getTypeName(); } else { mConnectedNetworkType = ConnectivityManager.TYPE_NONE; mConnectedNetworkTypeName = null; } int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0); if (CHATTY) { Log.d(TAG, "updateConnectivity: networkInfo=" + info); Log.d(TAG, "updateConnectivity: connectionStatus=" + connectionStatus); } mInetCondition = (connectionStatus > INET_CONDITION_THRESHOLD ? 1 : 0); if (info != null && info.getType() == ConnectivityManager.TYPE_BLUETOOTH) { mBluetoothTethered = info.isConnected(); } else { mBluetoothTethered = false; } // We want to update all the icons, all at once, for any condition change updateDataNetType(); updateWimaxIcons(); updateDataIcon(); updateTelephonySignalStrength(); updateWifiIcons(); } // ===== Update the views ======================================================= void refreshViews() { Context context = mContext; int combinedSignalIconId = 0; String combinedLabel = ""; String wifiLabel = ""; String mobileLabel = ""; int N; final boolean emergencyOnly = isEmergencyOnly(); if (!mHasMobileDataFeature) { mDataSignalIconId = mPhoneSignalIconId = 0; mQSPhoneSignalIconId = 0; mobileLabel = ""; } else { // We want to show the carrier name if in service and either: // - We are connected to mobile data, or // - We are not connected to mobile data, as long as the *reason* packets are not // being routed over that link is that we have better connectivity via wifi. // If data is disconnected for some other reason but wifi (or ethernet/bluetooth) // is connected, we show nothing. // Otherwise (nothing connected) we show "No internet connection". if (mDataConnected) { mobileLabel = mNetworkName; } else if (mConnected || emergencyOnly) { if (hasService() || emergencyOnly) { // The isEmergencyOnly test covers the case of a phone with no SIM mobileLabel = mNetworkName; } else { // Tablets, basically mobileLabel = ""; } } else { mobileLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); } // Now for things that should only be shown when actually using mobile data. if (mDataConnected) { combinedSignalIconId = mDataSignalIconId; combinedLabel = mobileLabel; combinedSignalIconId = mDataSignalIconId; // set by updateDataIcon() mContentDescriptionCombinedSignal = mContentDescriptionDataType; } } if (mWifiConnected) { if (mWifiSsid == null) { wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_wifi_nossid); } else { wifiLabel = mWifiSsid; if (DEBUG) { wifiLabel += "xxxxXXXXxxxxXXXX"; } } combinedLabel = wifiLabel; combinedSignalIconId = mWifiIconId; // set by updateWifiIcons() mContentDescriptionCombinedSignal = mContentDescriptionWifi; } else { if (mHasMobileDataFeature) { wifiLabel = ""; } else { wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); } } if (mBluetoothTethered) { combinedLabel = mContext.getString(R.string.bluetooth_tethered); combinedSignalIconId = mBluetoothTetherIconId; mContentDescriptionCombinedSignal = mContext.getString( R.string.accessibility_bluetooth_tether); } final boolean ethernetConnected = (mConnectedNetworkType == ConnectivityManager.TYPE_ETHERNET); if (ethernetConnected) { combinedLabel = context.getString(R.string.ethernet_label); } if (mAirplaneMode && (mServiceState == null || (!hasService() && !mServiceState.isEmergencyOnly()))) { // Only display the flight-mode icon if not in "emergency calls only" mode. // look again; your radios are now airplanes mContentDescriptionPhoneSignal = mContext.getString( R.string.accessibility_airplane_mode); mAirplaneIconId = TelephonyIcons.FLIGHT_MODE_ICON; mPhoneSignalIconId = mDataSignalIconId = mDataTypeIconId = mQSDataTypeIconId = 0; mQSPhoneSignalIconId = 0; // combined values from connected wifi take precedence over airplane mode if (mWifiConnected) { // Suppress "No internet connection." from mobile if wifi connected. mobileLabel = ""; } else { if (mHasMobileDataFeature) { // let the mobile icon show "No internet connection." wifiLabel = ""; } else { wifiLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); combinedLabel = wifiLabel; } mContentDescriptionCombinedSignal = mContentDescriptionPhoneSignal; combinedSignalIconId = mDataSignalIconId; } } else if (!mDataConnected && !mWifiConnected && !mBluetoothTethered && !mWimaxConnected && !ethernetConnected) { // pretty much totally disconnected combinedLabel = context.getString(R.string.status_bar_settings_signal_meter_disconnected); // On devices without mobile radios, we want to show the wifi icon combinedSignalIconId = mHasMobileDataFeature ? mDataSignalIconId : mWifiIconId; mContentDescriptionCombinedSignal = mHasMobileDataFeature ? mContentDescriptionDataType : mContentDescriptionWifi; int inetCondition = inetConditionForNetwork(ConnectivityManager.TYPE_MOBILE); mDataTypeIconId = 0; mQSDataTypeIconId = 0; if (isRoaming()) { mDataTypeIconId = TelephonyIcons.ROAMING_ICON; mQSDataTypeIconId = TelephonyIcons.QS_DATA_R[mInetCondition]; } } if (mDemoMode) { mQSWifiIconId = mDemoWifiLevel < 0 ? R.drawable.ic_qs_wifi_no_network : WifiIcons.QS_WIFI_SIGNAL_STRENGTH[mDemoInetCondition][mDemoWifiLevel]; mQSPhoneSignalIconId = mDemoMobileLevel < 0 ? R.drawable.ic_qs_signal_no_signal : TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH[mDemoInetCondition][mDemoMobileLevel]; mQSDataTypeIconId = mDemoQSDataTypeIconId; } if (DEBUG) { Log.d(TAG, "refreshViews connected={" + (mWifiConnected?" wifi":"") + (mDataConnected?" data":"") + " } level=" + ((mSignalStrength == null)?"??":Integer.toString(mSignalStrength.getLevel())) + " combinedSignalIconId=0x" + Integer.toHexString(combinedSignalIconId) + "/" + getResourceName(combinedSignalIconId) + " mobileLabel=" + mobileLabel + " wifiLabel=" + wifiLabel + " emergencyOnly=" + emergencyOnly + " combinedLabel=" + combinedLabel + " mAirplaneMode=" + mAirplaneMode + " mDataActivity=" + mDataActivity + " mPhoneSignalIconId=0x" + Integer.toHexString(mPhoneSignalIconId) + " mQSPhoneSignalIconId=0x" + Integer.toHexString(mQSPhoneSignalIconId) + " mDataDirectionIconId=0x" + Integer.toHexString(mDataDirectionIconId) + " mDataSignalIconId=0x" + Integer.toHexString(mDataSignalIconId) + " mDataTypeIconId=0x" + Integer.toHexString(mDataTypeIconId) + " mQSDataTypeIconId=0x" + Integer.toHexString(mQSDataTypeIconId) + " mWifiIconId=0x" + Integer.toHexString(mWifiIconId) + " mQSWifiIconId=0x" + Integer.toHexString(mQSWifiIconId) + " mBluetoothTetherIconId=0x" + Integer.toHexString(mBluetoothTetherIconId)); } // update QS for (NetworkSignalChangedCallback cb : mSignalsChangedCallbacks) { notifySignalsChangedCallbacks(cb); } if (mLastPhoneSignalIconId != mPhoneSignalIconId || mLastWifiIconId != mWifiIconId || mLastInetCondition != mInetCondition || mLastWimaxIconId != mWimaxIconId || mLastDataTypeIconId != mDataTypeIconId || mLastAirplaneMode != mAirplaneMode || mLastLocale != mLocale || mLastConnectedNetworkType != mConnectedNetworkType) { // NB: the mLast*s will be updated later for (SignalCluster cluster : mSignalClusters) { refreshSignalCluster(cluster); } } if (mLastAirplaneMode != mAirplaneMode) { mLastAirplaneMode = mAirplaneMode; } if (mLastLocale != mLocale) { mLastLocale = mLocale; } // the phone icon on phones if (mLastPhoneSignalIconId != mPhoneSignalIconId) { mLastPhoneSignalIconId = mPhoneSignalIconId; } // the data icon on phones if (mLastDataDirectionIconId != mDataDirectionIconId) { mLastDataDirectionIconId = mDataDirectionIconId; } // the wifi icon on phones if (mLastWifiIconId != mWifiIconId) { mLastWifiIconId = mWifiIconId; } if (mLastInetCondition != mInetCondition) { mLastInetCondition = mInetCondition; } if (mLastConnectedNetworkType != mConnectedNetworkType) { mLastConnectedNetworkType = mConnectedNetworkType; } // the wimax icon on phones if (mLastWimaxIconId != mWimaxIconId) { mLastWimaxIconId = mWimaxIconId; } // the combined data signal icon if (mLastCombinedSignalIconId != combinedSignalIconId) { mLastCombinedSignalIconId = combinedSignalIconId; } // the data network type overlay if (mLastDataTypeIconId != mDataTypeIconId) { mLastDataTypeIconId = mDataTypeIconId; } // the combinedLabel in the notification panel if (!mLastCombinedLabel.equals(combinedLabel)) { mLastCombinedLabel = combinedLabel; N = mCombinedLabelViews.size(); for (int i=0; i