Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.systemui.statusbar.policy;
     18 
     19 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
     20 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
     21 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
     22 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
     23 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
     24 import android.content.BroadcastReceiver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.IntentFilter;
     28 import android.content.res.Resources;
     29 import android.net.ConnectivityManager;
     30 import android.net.NetworkCapabilities;
     31 import android.net.NetworkInfo;
     32 import android.net.wifi.WifiConfiguration;
     33 import android.net.wifi.WifiInfo;
     34 import android.net.wifi.WifiManager;
     35 import android.os.AsyncTask;
     36 import android.os.Bundle;
     37 import android.os.Handler;
     38 import android.os.Message;
     39 import android.os.Messenger;
     40 import android.provider.Settings;
     41 import android.telephony.PhoneStateListener;
     42 import android.telephony.ServiceState;
     43 import android.telephony.SignalStrength;
     44 import android.telephony.SubscriptionInfo;
     45 import android.telephony.SubscriptionManager;
     46 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
     47 import android.telephony.TelephonyManager;
     48 import android.text.TextUtils;
     49 import android.text.format.DateFormat;
     50 import android.util.Log;
     51 import android.util.SparseArray;
     52 
     53 import com.android.internal.annotations.VisibleForTesting;
     54 import com.android.internal.telephony.IccCardConstants;
     55 import com.android.internal.telephony.PhoneConstants;
     56 import com.android.internal.telephony.TelephonyIntents;
     57 import com.android.internal.telephony.cdma.EriInfo;
     58 import com.android.internal.util.AsyncChannel;
     59 import com.android.systemui.DemoMode;
     60 import com.android.systemui.R;
     61 
     62 import java.io.FileDescriptor;
     63 import java.io.PrintWriter;
     64 import java.util.ArrayList;
     65 import java.util.BitSet;
     66 import java.util.Collections;
     67 import java.util.Comparator;
     68 import java.util.HashMap;
     69 import java.util.List;
     70 import java.util.Locale;
     71 import java.util.Map;
     72 import java.util.Objects;
     73 
     74 /** Platform implementation of the network controller. **/
     75 public class NetworkControllerImpl extends BroadcastReceiver
     76         implements NetworkController, DemoMode {
     77     // debug
     78     static final String TAG = "NetworkController";
     79     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     80     // additional diagnostics, but not logspew
     81     static final boolean CHATTY =  Log.isLoggable(TAG + ".Chat", Log.DEBUG);
     82     // Save the previous SignalController.States of all SignalControllers for dumps.
     83     static final boolean RECORD_HISTORY = true;
     84     // If RECORD_HISTORY how many to save, must be a power of 2.
     85     static final int HISTORY_SIZE = 16;
     86 
     87     private static final int INET_CONDITION_THRESHOLD = 50;
     88 
     89     private final Context mContext;
     90     private final TelephonyManager mPhone;
     91     private final WifiManager mWifiManager;
     92     private final ConnectivityManager mConnectivityManager;
     93     private final SubscriptionManager mSubscriptionManager;
     94     private final boolean mHasMobileDataFeature;
     95     private Config mConfig;
     96 
     97     // Subcontrollers.
     98     @VisibleForTesting
     99     final WifiSignalController mWifiSignalController;
    100     @VisibleForTesting
    101     final Map<Integer, MobileSignalController> mMobileSignalControllers =
    102             new HashMap<Integer, MobileSignalController>();
    103     // When no SIMs are around at setup, and one is added later, it seems to default to the first
    104     // SIM for most actions.  This may be null if there aren't any SIMs around.
    105     private MobileSignalController mDefaultSignalController;
    106     private final AccessPointControllerImpl mAccessPoints;
    107     private final MobileDataControllerImpl mMobileDataController;
    108 
    109     // Network types that replace the carrier label if the device does not support mobile data.
    110     private boolean mBluetoothTethered = false;
    111     private boolean mEthernetConnected = false;
    112 
    113     // state of inet connection
    114     private boolean mConnected = false;
    115     private boolean mInetCondition; // Used for Logging and demo.
    116 
    117     // BitSets indicating which network transport types (e.g., TRANSPORT_WIFI, TRANSPORT_MOBILE) are
    118     // connected and validated, respectively.
    119     private final BitSet mConnectedTransports = new BitSet();
    120     private final BitSet mValidatedTransports = new BitSet();
    121 
    122     // States that don't belong to a subcontroller.
    123     private boolean mAirplaneMode = false;
    124     private boolean mHasNoSims;
    125     private Locale mLocale = null;
    126     // This list holds our ordering.
    127     private List<SubscriptionInfo> mCurrentSubscriptions
    128             = new ArrayList<SubscriptionInfo>();
    129 
    130     // All the callbacks.
    131     private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
    132     private ArrayList<CarrierLabelListener> mCarrierListeners =
    133             new ArrayList<CarrierLabelListener>();
    134     private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
    135     private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
    136             new ArrayList<NetworkSignalChangedCallback>();
    137     @VisibleForTesting
    138     boolean mListening;
    139 
    140     // The current user ID.
    141     private int mCurrentUserId;
    142 
    143     /**
    144      * Construct this controller object and register for updates.
    145      */
    146     public NetworkControllerImpl(Context context) {
    147         this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
    148                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
    149                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
    150                 SubscriptionManager.from(context), Config.readConfig(context),
    151                 new AccessPointControllerImpl(context), new MobileDataControllerImpl(context));
    152         registerListeners();
    153     }
    154 
    155     @VisibleForTesting
    156     NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
    157             TelephonyManager telephonyManager, WifiManager wifiManager,
    158             SubscriptionManager subManager, Config config,
    159             AccessPointControllerImpl accessPointController,
    160             MobileDataControllerImpl mobileDataController) {
    161         mContext = context;
    162         mConfig = config;
    163 
    164         mSubscriptionManager = subManager;
    165         mConnectivityManager = connectivityManager;
    166         mHasMobileDataFeature =
    167                 mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
    168 
    169         // telephony
    170         mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
    171 
    172         // wifi
    173         mWifiManager = wifiManager;
    174 
    175         mLocale = mContext.getResources().getConfiguration().locale;
    176         mAccessPoints = accessPointController;
    177         mMobileDataController = mobileDataController;
    178         mMobileDataController.setNetworkController(this);
    179         // TODO: Find a way to move this into MobileDataController.
    180         mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
    181             @Override
    182             public void onMobileDataEnabled(boolean enabled) {
    183                 notifyMobileDataEnabled(enabled);
    184             }
    185         });
    186         mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
    187                 mSignalsChangedCallbacks, mSignalClusters, this);
    188 
    189         // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
    190         updateAirplaneMode(true /* force callback */);
    191         mAccessPoints.setNetworkController(this);
    192     }
    193 
    194     private void registerListeners() {
    195         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    196             mobileSignalController.registerListener();
    197         }
    198         mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
    199 
    200         // broadcasts
    201         IntentFilter filter = new IntentFilter();
    202         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
    203         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    204         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    205         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    206         filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
    207         filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
    208         filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
    209         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
    210         filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
    211         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
    212         filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    213         mContext.registerReceiver(this, filter);
    214         mListening = true;
    215 
    216         updateMobileControllers();
    217     }
    218 
    219     private void unregisterListeners() {
    220         mListening = false;
    221         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    222             mobileSignalController.unregisterListener();
    223         }
    224         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
    225         mContext.unregisterReceiver(this);
    226     }
    227 
    228     public int getConnectedWifiLevel() {
    229         return mWifiSignalController.getState().level;
    230     }
    231 
    232     @Override
    233     public AccessPointController getAccessPointController() {
    234         return mAccessPoints;
    235     }
    236 
    237     @Override
    238     public MobileDataController getMobileDataController() {
    239         return mMobileDataController;
    240     }
    241 
    242     public void addEmergencyListener(EmergencyListener listener) {
    243         mEmergencyListeners.add(listener);
    244         listener.setEmergencyCallsOnly(isEmergencyOnly());
    245     }
    246 
    247     public void addCarrierLabel(CarrierLabelListener listener) {
    248         mCarrierListeners.add(listener);
    249         refreshCarrierLabel();
    250     }
    251 
    252     private void notifyMobileDataEnabled(boolean enabled) {
    253         final int length = mSignalsChangedCallbacks.size();
    254         for (int i = 0; i < length; i++) {
    255             mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
    256         }
    257     }
    258 
    259     public boolean hasMobileDataFeature() {
    260         return mHasMobileDataFeature;
    261     }
    262 
    263     public boolean hasVoiceCallingFeature() {
    264         return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
    265     }
    266 
    267     private MobileSignalController getDataController() {
    268         int dataSubId = SubscriptionManager.getDefaultDataSubId();
    269         if (!SubscriptionManager.isValidSubscriptionId(dataSubId)) {
    270             if (DEBUG) Log.e(TAG, "No data sim selected");
    271             return mDefaultSignalController;
    272         }
    273         if (mMobileSignalControllers.containsKey(dataSubId)) {
    274             return mMobileSignalControllers.get(dataSubId);
    275         }
    276         if (DEBUG) Log.e(TAG, "Cannot find controller for data sub: " + dataSubId);
    277         return mDefaultSignalController;
    278     }
    279 
    280     public String getMobileNetworkName() {
    281         MobileSignalController controller = getDataController();
    282         return controller != null ? controller.getState().networkName : "";
    283     }
    284 
    285     public boolean isEmergencyOnly() {
    286         int voiceSubId = SubscriptionManager.getDefaultVoiceSubId();
    287         if (!SubscriptionManager.isValidSubscriptionId(voiceSubId)) {
    288             for (MobileSignalController mobileSignalController :
    289                                             mMobileSignalControllers.values()) {
    290                 if (!mobileSignalController.isEmergencyOnly()) {
    291                     return false;
    292                 }
    293             }
    294         }
    295         if (mMobileSignalControllers.containsKey(voiceSubId)) {
    296             return mMobileSignalControllers.get(voiceSubId).isEmergencyOnly();
    297         }
    298         if (DEBUG) Log.e(TAG, "Cannot find controller for voice sub: " + voiceSubId);
    299         // Something is wrong, better assume we can't make calls...
    300         return true;
    301     }
    302 
    303     /**
    304      * Emergency status may have changed (triggered by MobileSignalController),
    305      * so we should recheck and send out the state to listeners.
    306      */
    307     void recalculateEmergency() {
    308         final boolean emergencyOnly = isEmergencyOnly();
    309         final int length = mEmergencyListeners.size();
    310         for (int i = 0; i < length; i++) {
    311             mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
    312         }
    313         // If the emergency has a chance to change, then so does the carrier
    314         // label.
    315         refreshCarrierLabel();
    316     }
    317 
    318     public void addSignalCluster(SignalCluster cluster) {
    319         mSignalClusters.add(cluster);
    320         cluster.setSubs(mCurrentSubscriptions);
    321         cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
    322                 R.string.accessibility_airplane_mode);
    323         cluster.setNoSims(mHasNoSims);
    324         mWifiSignalController.notifyListeners();
    325         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    326             mobileSignalController.notifyListeners();
    327         }
    328     }
    329 
    330     public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
    331         mSignalsChangedCallbacks.add(cb);
    332         cb.onAirplaneModeChanged(mAirplaneMode);
    333         cb.onNoSimVisibleChanged(mHasNoSims);
    334         mWifiSignalController.notifyListeners();
    335         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    336             mobileSignalController.notifyListeners();
    337         }
    338     }
    339 
    340     public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
    341         mSignalsChangedCallbacks.remove(cb);
    342     }
    343 
    344     @Override
    345     public void setWifiEnabled(final boolean enabled) {
    346         new AsyncTask<Void, Void, Void>() {
    347             @Override
    348             protected Void doInBackground(Void... args) {
    349                 // Disable tethering if enabling Wifi
    350                 final int wifiApState = mWifiManager.getWifiApState();
    351                 if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
    352                         (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
    353                     mWifiManager.setWifiApEnabled(null, false);
    354                 }
    355 
    356                 mWifiManager.setWifiEnabled(enabled);
    357                 return null;
    358             }
    359         }.execute();
    360     }
    361 
    362     @Override
    363     public void onUserSwitched(int newUserId) {
    364         mCurrentUserId = newUserId;
    365         mAccessPoints.onUserSwitched(newUserId);
    366         updateConnectivity();
    367         refreshCarrierLabel();
    368     }
    369 
    370     @Override
    371     public void onReceive(Context context, Intent intent) {
    372         if (CHATTY) {
    373             Log.d(TAG, "onReceive: intent=" + intent);
    374         }
    375         final String action = intent.getAction();
    376         if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
    377                 action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
    378             updateConnectivity();
    379             refreshCarrierLabel();
    380         } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
    381             mConfig = Config.readConfig(mContext);
    382             handleConfigurationChanged();
    383         } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
    384             refreshLocale();
    385             updateAirplaneMode(false);
    386             refreshCarrierLabel();
    387         } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED)) {
    388             // We are using different subs now, we might be able to make calls.
    389             recalculateEmergency();
    390         } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
    391             // Notify every MobileSignalController so they can know whether they are the
    392             // data sim or not.
    393             for (MobileSignalController controller : mMobileSignalControllers.values()) {
    394                 controller.handleBroadcast(intent);
    395             }
    396         } else if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
    397             // Might have different subscriptions now.
    398             updateMobileControllers();
    399         } else {
    400             int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
    401                     SubscriptionManager.INVALID_SUBSCRIPTION_ID);
    402             if (SubscriptionManager.isValidSubscriptionId(subId)) {
    403                 if (mMobileSignalControllers.containsKey(subId)) {
    404                     mMobileSignalControllers.get(subId).handleBroadcast(intent);
    405                 } else {
    406                     // Can't find this subscription...  We must be out of date.
    407                     updateMobileControllers();
    408                 }
    409             } else {
    410                 // No sub id, must be for the wifi.
    411                 mWifiSignalController.handleBroadcast(intent);
    412             }
    413         }
    414     }
    415 
    416     @VisibleForTesting
    417     void handleConfigurationChanged() {
    418         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    419             mobileSignalController.setConfiguration(mConfig);
    420         }
    421         refreshLocale();
    422         refreshCarrierLabel();
    423     }
    424 
    425     private void updateMobileControllers() {
    426         if (!mListening) {
    427             return;
    428         }
    429         List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
    430         if (subscriptions == null) {
    431             subscriptions = Collections.emptyList();
    432         }
    433         // If there have been no relevant changes to any of the subscriptions, we can leave as is.
    434         if (hasCorrectMobileControllers(subscriptions)) {
    435             // Even if the controllers are correct, make sure we have the right no sims state.
    436             // Such as on boot, don't need any controllers, because there are no sims,
    437             // but we still need to update the no sim state.
    438             updateNoSims();
    439             return;
    440         }
    441         setCurrentSubscriptions(subscriptions);
    442         updateNoSims();
    443     }
    444 
    445     @VisibleForTesting
    446     protected void updateNoSims() {
    447         boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
    448         if (hasNoSims != mHasNoSims) {
    449             mHasNoSims = hasNoSims;
    450             notifyListeners();
    451         }
    452     }
    453 
    454     @VisibleForTesting
    455     void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
    456         Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
    457             @Override
    458             public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
    459                 return lhs.getSimSlotIndex() == rhs.getSimSlotIndex()
    460                         ? lhs.getSubscriptionId() - rhs.getSubscriptionId()
    461                         : lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
    462             }
    463         });
    464         final int length = mSignalClusters.size();
    465         for (int i = 0; i < length; i++) {
    466             mSignalClusters.get(i).setSubs(subscriptions);
    467         }
    468         mCurrentSubscriptions = subscriptions;
    469 
    470         HashMap<Integer, MobileSignalController> cachedControllers =
    471                 new HashMap<Integer, MobileSignalController>(mMobileSignalControllers);
    472         mMobileSignalControllers.clear();
    473         final int num = subscriptions.size();
    474         for (int i = 0; i < num; i++) {
    475             int subId = subscriptions.get(i).getSubscriptionId();
    476             // If we have a copy of this controller already reuse it, otherwise make a new one.
    477             if (cachedControllers.containsKey(subId)) {
    478                 mMobileSignalControllers.put(subId, cachedControllers.remove(subId));
    479             } else {
    480                 MobileSignalController controller = new MobileSignalController(mContext, mConfig,
    481                         mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters,
    482                         this, subscriptions.get(i));
    483                 mMobileSignalControllers.put(subId, controller);
    484                 if (subscriptions.get(i).getSimSlotIndex() == 0) {
    485                     mDefaultSignalController = controller;
    486                 }
    487                 if (mListening) {
    488                     controller.registerListener();
    489                 }
    490             }
    491         }
    492         if (mListening) {
    493             for (Integer key : cachedControllers.keySet()) {
    494                 if (cachedControllers.get(key) == mDefaultSignalController) {
    495                     mDefaultSignalController = null;
    496                 }
    497                 cachedControllers.get(key).unregisterListener();
    498             }
    499         }
    500         // There may be new MobileSignalControllers around, make sure they get the current
    501         // inet condition and airplane mode.
    502         pushConnectivityToSignals();
    503         updateAirplaneMode(true /* force */);
    504     }
    505 
    506     @VisibleForTesting
    507     boolean hasCorrectMobileControllers(List<SubscriptionInfo> allSubscriptions) {
    508         if (allSubscriptions.size() != mMobileSignalControllers.size()) {
    509             return false;
    510         }
    511         for (SubscriptionInfo info : allSubscriptions) {
    512             if (!mMobileSignalControllers.containsKey(info.getSubscriptionId())) {
    513                 return false;
    514             }
    515         }
    516         return true;
    517     }
    518 
    519     private void updateAirplaneMode(boolean force) {
    520         boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
    521                 Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
    522         if (airplaneMode != mAirplaneMode || force) {
    523             mAirplaneMode = airplaneMode;
    524             for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    525                 mobileSignalController.setAirplaneMode(mAirplaneMode);
    526             }
    527             notifyListeners();
    528             refreshCarrierLabel();
    529         }
    530     }
    531 
    532     private void refreshLocale() {
    533         Locale current = mContext.getResources().getConfiguration().locale;
    534         if (!current.equals(mLocale)) {
    535             mLocale = current;
    536             notifyAllListeners();
    537         }
    538     }
    539 
    540     /**
    541      * Forces update of all callbacks on both SignalClusters and
    542      * NetworkSignalChangedCallbacks.
    543      */
    544     private void notifyAllListeners() {
    545         notifyListeners();
    546         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    547             mobileSignalController.notifyListeners();
    548         }
    549         mWifiSignalController.notifyListeners();
    550     }
    551 
    552     /**
    553      * Notifies listeners of changes in state of to the NetworkController, but
    554      * does not notify for any info on SignalControllers, for that call
    555      * notifyAllListeners.
    556      */
    557     private void notifyListeners() {
    558         int length = mSignalClusters.size();
    559         for (int i = 0; i < length; i++) {
    560             mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
    561                     R.string.accessibility_airplane_mode);
    562             mSignalClusters.get(i).setNoSims(mHasNoSims);
    563         }
    564         int signalsChangedLength = mSignalsChangedCallbacks.size();
    565         for (int i = 0; i < signalsChangedLength; i++) {
    566             mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
    567             mSignalsChangedCallbacks.get(i).onNoSimVisibleChanged(mHasNoSims);
    568         }
    569     }
    570 
    571     /**
    572      * Update the Inet conditions and what network we are connected to.
    573      */
    574     private void updateConnectivity() {
    575         mConnectedTransports.clear();
    576         mValidatedTransports.clear();
    577         for (NetworkCapabilities nc :
    578                 mConnectivityManager.getDefaultNetworkCapabilitiesForUser(mCurrentUserId)) {
    579             for (int transportType : nc.getTransportTypes()) {
    580                 mConnectedTransports.set(transportType);
    581                 if (nc.hasCapability(NET_CAPABILITY_VALIDATED)) {
    582                     mValidatedTransports.set(transportType);
    583                 }
    584             }
    585         }
    586 
    587         if (CHATTY) {
    588             Log.d(TAG, "updateConnectivity: mConnectedTransports=" + mConnectedTransports);
    589             Log.d(TAG, "updateConnectivity: mValidatedTransports=" + mValidatedTransports);
    590         }
    591 
    592         mConnected = !mConnectedTransports.isEmpty();
    593         mInetCondition = !mValidatedTransports.isEmpty();
    594         mBluetoothTethered = mConnectedTransports.get(TRANSPORT_BLUETOOTH);
    595         mEthernetConnected = mConnectedTransports.get(TRANSPORT_ETHERNET);
    596 
    597         pushConnectivityToSignals();
    598     }
    599 
    600     /**
    601      * Pushes the current connectivity state to all SignalControllers.
    602      */
    603     private void pushConnectivityToSignals() {
    604         // We want to update all the icons, all at once, for any condition change
    605         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    606             mobileSignalController.setInetCondition(
    607                     mInetCondition ? 1 : 0,
    608                     mValidatedTransports.get(mobileSignalController.getTransportType()) ? 1 : 0);
    609         }
    610         mWifiSignalController.setInetCondition(
    611                 mValidatedTransports.get(mWifiSignalController.getTransportType()) ? 1 : 0);
    612     }
    613 
    614     /**
    615      * Recalculate and update the carrier label.
    616      */
    617     void refreshCarrierLabel() {
    618         Context context = mContext;
    619 
    620         WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
    621         String label = "";
    622         for (MobileSignalController controller : mMobileSignalControllers.values()) {
    623             label = controller.getLabel(label, mConnected, mHasMobileDataFeature);
    624         }
    625 
    626         // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
    627         // but stay for the sake of history.
    628         if (mBluetoothTethered && !mHasMobileDataFeature) {
    629             label = mContext.getString(R.string.bluetooth_tethered);
    630         }
    631 
    632         if (mEthernetConnected && !mHasMobileDataFeature) {
    633             label = context.getString(R.string.ethernet_label);
    634         }
    635 
    636         if (mAirplaneMode && !isEmergencyOnly()) {
    637             // combined values from connected wifi take precedence over airplane mode
    638             if (wifiState.connected && mHasMobileDataFeature) {
    639                 // Suppress "No internet connection." from mobile if wifi connected.
    640                 label = "";
    641             } else {
    642                  if (!mHasMobileDataFeature) {
    643                       label = context.getString(
    644                               R.string.status_bar_settings_signal_meter_disconnected);
    645                  }
    646             }
    647         } else if (!isMobileDataConnected() && !wifiState.connected && !mBluetoothTethered &&
    648                  !mEthernetConnected && !mHasMobileDataFeature) {
    649             // Pretty much no connection.
    650             label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
    651         }
    652 
    653         // for mobile devices, we always show mobile connection info here (SPN/PLMN)
    654         // for other devices, we show whatever network is connected
    655         // This is determined above by references to mHasMobileDataFeature.
    656         int length = mCarrierListeners.size();
    657         for (int i = 0; i < length; i++) {
    658             mCarrierListeners.get(i).setCarrierLabel(label);
    659         }
    660     }
    661 
    662     private boolean isMobileDataConnected() {
    663         MobileSignalController controller = getDataController();
    664         return controller != null ? controller.getState().dataConnected : false;
    665     }
    666 
    667     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    668         pw.println("NetworkController state:");
    669 
    670         pw.println("  - telephony ------");
    671         pw.print("  hasVoiceCallingFeature()=");
    672         pw.println(hasVoiceCallingFeature());
    673 
    674         pw.println("  - Bluetooth ----");
    675         pw.print("  mBtReverseTethered=");
    676         pw.println(mBluetoothTethered);
    677 
    678         pw.println("  - connectivity ------");
    679         pw.print("  mConnectedTransports=");
    680         pw.println(mConnectedTransports);
    681         pw.print("  mValidatedTransports=");
    682         pw.println(mValidatedTransports);
    683         pw.print("  mInetCondition=");
    684         pw.println(mInetCondition);
    685         pw.print("  mAirplaneMode=");
    686         pw.println(mAirplaneMode);
    687         pw.print("  mLocale=");
    688         pw.println(mLocale);
    689 
    690         for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) {
    691             mobileSignalController.dump(pw);
    692         }
    693         mWifiSignalController.dump(pw);
    694     }
    695 
    696     private boolean mDemoMode;
    697     private int mDemoInetCondition;
    698     private WifiSignalController.WifiState mDemoWifiState;
    699 
    700     @Override
    701     public void dispatchDemoCommand(String command, Bundle args) {
    702         if (!mDemoMode && command.equals(COMMAND_ENTER)) {
    703             if (DEBUG) Log.d(TAG, "Entering demo mode");
    704             unregisterListeners();
    705             mDemoMode = true;
    706             mDemoInetCondition = mInetCondition ? 1 : 0;
    707             mDemoWifiState = mWifiSignalController.getState();
    708         } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
    709             if (DEBUG) Log.d(TAG, "Exiting demo mode");
    710             mDemoMode = false;
    711             // Update what MobileSignalControllers, because they may change
    712             // to set the number of sim slots.
    713             updateMobileControllers();
    714             for (MobileSignalController controller : mMobileSignalControllers.values()) {
    715                 controller.resetLastState();
    716             }
    717             mWifiSignalController.resetLastState();
    718             registerListeners();
    719             notifyAllListeners();
    720             refreshCarrierLabel();
    721         } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
    722             String airplane = args.getString("airplane");
    723             if (airplane != null) {
    724                 boolean show = airplane.equals("show");
    725                 int length = mSignalClusters.size();
    726                 for (int i = 0; i < length; i++) {
    727                     mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
    728                             R.string.accessibility_airplane_mode);
    729                 }
    730             }
    731             String fully = args.getString("fully");
    732             if (fully != null) {
    733                 mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
    734                 mWifiSignalController.setInetCondition(mDemoInetCondition);
    735                 for (MobileSignalController controller : mMobileSignalControllers.values()) {
    736                     controller.setInetCondition(mDemoInetCondition, mDemoInetCondition);
    737                 }
    738             }
    739             String wifi = args.getString("wifi");
    740             if (wifi != null) {
    741                 boolean show = wifi.equals("show");
    742                 String level = args.getString("level");
    743                 if (level != null) {
    744                     mDemoWifiState.level = level.equals("null") ? -1
    745                             : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
    746                     mDemoWifiState.connected = mDemoWifiState.level >= 0;
    747                 }
    748                 mDemoWifiState.enabled = show;
    749                 mWifiSignalController.notifyListeners();
    750             }
    751             String sims = args.getString("sims");
    752             if (sims != null) {
    753                 int num = Integer.parseInt(sims);
    754                 List<SubscriptionInfo> subs = new ArrayList<SubscriptionInfo>();
    755                 if (num != mMobileSignalControllers.size()) {
    756                     mMobileSignalControllers.clear();
    757                     int start = mSubscriptionManager.getActiveSubscriptionInfoCountMax();
    758                     for (int i = start /* get out of normal index range */; i < start + num; i++) {
    759                         SubscriptionInfo info = new SubscriptionInfo(i, "", i, "", "", 0, 0, "", 0,
    760                                 null, 0, 0, "");
    761                         subs.add(info);
    762                         mMobileSignalControllers.put(i, new MobileSignalController(mContext,
    763                                 mConfig, mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks,
    764                                 mSignalClusters, this, info));
    765                     }
    766                 }
    767                 final int n = mSignalClusters.size();
    768                 for (int i = 0; i < n; i++) {
    769                     mSignalClusters.get(i).setSubs(subs);
    770                 }
    771             }
    772             String nosim = args.getString("nosim");
    773             if (nosim != null) {
    774                 boolean show = nosim.equals("show");
    775                 final int n = mSignalClusters.size();
    776                 for (int i = 0; i < n; i++) {
    777                     mSignalClusters.get(i).setNoSims(show);
    778                 }
    779             }
    780             String mobile = args.getString("mobile");
    781             if (mobile != null) {
    782                 boolean show = mobile.equals("show");
    783                 String datatype = args.getString("datatype");
    784                 String slotString = args.getString("slot");
    785                 int slot = TextUtils.isEmpty(slotString) ? 0 : Integer.parseInt(slotString);
    786                 // Hack to index linearly for easy use.
    787                 MobileSignalController controller = mMobileSignalControllers
    788                         .values().toArray(new MobileSignalController[0])[slot];
    789                 controller.getState().dataSim = datatype != null;
    790                 if (datatype != null) {
    791                     controller.getState().iconGroup =
    792                             datatype.equals("1x") ? TelephonyIcons.ONE_X :
    793                             datatype.equals("3g") ? TelephonyIcons.THREE_G :
    794                             datatype.equals("4g") ? TelephonyIcons.FOUR_G :
    795                             datatype.equals("e") ? TelephonyIcons.E :
    796                             datatype.equals("g") ? TelephonyIcons.G :
    797                             datatype.equals("h") ? TelephonyIcons.H :
    798                             datatype.equals("lte") ? TelephonyIcons.LTE :
    799                             datatype.equals("roam") ? TelephonyIcons.ROAMING :
    800                             TelephonyIcons.UNKNOWN;
    801                 }
    802                 int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
    803                 String level = args.getString("level");
    804                 if (level != null) {
    805                     controller.getState().level = level.equals("null") ? -1
    806                             : Math.min(Integer.parseInt(level), icons[0].length - 1);
    807                     controller.getState().connected = controller.getState().level >= 0;
    808                 }
    809                 controller.getState().enabled = show;
    810                 controller.notifyListeners();
    811             }
    812             refreshCarrierLabel();
    813         }
    814     }
    815 
    816     private final OnSubscriptionsChangedListener mSubscriptionListener =
    817             new OnSubscriptionsChangedListener() {
    818         @Override
    819         public void onSubscriptionsChanged() {
    820             updateMobileControllers();
    821         };
    822     };
    823 
    824     // TODO: Move to its own file.
    825     static class WifiSignalController extends
    826             SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
    827         private final WifiManager mWifiManager;
    828         private final AsyncChannel mWifiChannel;
    829         private final boolean mHasMobileData;
    830 
    831         public WifiSignalController(Context context, boolean hasMobileData,
    832                 List<NetworkSignalChangedCallback> signalCallbacks,
    833                 List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
    834             super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
    835                     signalCallbacks, signalClusters, networkController);
    836             mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    837             mHasMobileData = hasMobileData;
    838             Handler handler = new WifiHandler();
    839             mWifiChannel = new AsyncChannel();
    840             Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
    841             if (wifiMessenger != null) {
    842                 mWifiChannel.connect(context, handler, wifiMessenger);
    843             }
    844             // WiFi only has one state.
    845             mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
    846                     "Wi-Fi Icons",
    847                     WifiIcons.WIFI_SIGNAL_STRENGTH,
    848                     WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
    849                     AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
    850                     WifiIcons.WIFI_NO_NETWORK,
    851                     WifiIcons.QS_WIFI_NO_NETWORK,
    852                     WifiIcons.WIFI_NO_NETWORK,
    853                     WifiIcons.QS_WIFI_NO_NETWORK,
    854                     AccessibilityContentDescriptions.WIFI_NO_CONNECTION
    855                     );
    856         }
    857 
    858         @Override
    859         protected WifiState cleanState() {
    860             return new WifiState();
    861         }
    862 
    863         @Override
    864         public void notifyListeners() {
    865             // only show wifi in the cluster if connected or if wifi-only
    866             boolean wifiVisible = mCurrentState.enabled
    867                     && (mCurrentState.connected || !mHasMobileData);
    868             String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
    869             boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
    870             String contentDescription = getStringIfExists(getContentDescription());
    871             int length = mSignalsChangedCallbacks.size();
    872             for (int i = 0; i < length; i++) {
    873                 mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
    874                         mCurrentState.connected, getQsCurrentIconId(),
    875                         ssidPresent && mCurrentState.activityIn,
    876                         ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
    877             }
    878 
    879             int signalClustersLength = mSignalClusters.size();
    880             for (int i = 0; i < signalClustersLength; i++) {
    881                 mSignalClusters.get(i).setWifiIndicators(wifiVisible, getCurrentIconId(),
    882                         contentDescription);
    883             }
    884         }
    885 
    886         /**
    887          * Extract wifi state directly from broadcasts about changes in wifi state.
    888          */
    889         public void handleBroadcast(Intent intent) {
    890             String action = intent.getAction();
    891             if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
    892                 mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    893                         WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
    894             } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    895                 final NetworkInfo networkInfo = (NetworkInfo)
    896                         intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    897                 mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
    898                 // If Connected grab the signal strength and ssid.
    899                 if (mCurrentState.connected) {
    900                     // try getting it out of the intent first
    901                     WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
    902                             ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
    903                             : mWifiManager.getConnectionInfo();
    904                     if (info != null) {
    905                         mCurrentState.ssid = getSsid(info);
    906                     } else {
    907                         mCurrentState.ssid = null;
    908                     }
    909                 } else if (!mCurrentState.connected) {
    910                     mCurrentState.ssid = null;
    911                 }
    912             } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
    913                 // Default to -200 as its below WifiManager.MIN_RSSI.
    914                 mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
    915                 mCurrentState.level = WifiManager.calculateSignalLevel(
    916                         mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
    917             }
    918 
    919             notifyListenersIfNecessary();
    920         }
    921 
    922         private String getSsid(WifiInfo info) {
    923             String ssid = info.getSSID();
    924             if (ssid != null) {
    925                 return ssid;
    926             }
    927             // OK, it's not in the connectionInfo; we have to go hunting for it
    928             List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
    929             int length = networks.size();
    930             for (int i = 0; i < length; i++) {
    931                 if (networks.get(i).networkId == info.getNetworkId()) {
    932                     return networks.get(i).SSID;
    933                 }
    934             }
    935             return null;
    936         }
    937 
    938         @VisibleForTesting
    939         void setActivity(int wifiActivity) {
    940             mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
    941                     || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
    942             mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
    943                     || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
    944             notifyListenersIfNecessary();
    945         }
    946 
    947         /**
    948          * Handler to receive the data activity on wifi.
    949          */
    950         class WifiHandler extends Handler {
    951             @Override
    952             public void handleMessage(Message msg) {
    953                 switch (msg.what) {
    954                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
    955                         if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
    956                             mWifiChannel.sendMessage(Message.obtain(this,
    957                                     AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
    958                         } else {
    959                             Log.e(mTag, "Failed to connect to wifi");
    960                         }
    961                         break;
    962                     case WifiManager.DATA_ACTIVITY_NOTIFICATION:
    963                         setActivity(msg.arg1);
    964                         break;
    965                     default:
    966                         // Ignore
    967                         break;
    968                 }
    969             }
    970         }
    971 
    972         static class WifiState extends SignalController.State {
    973             String ssid;
    974 
    975             @Override
    976             public void copyFrom(State s) {
    977                 super.copyFrom(s);
    978                 WifiState state = (WifiState) s;
    979                 ssid = state.ssid;
    980             }
    981 
    982             @Override
    983             protected void toString(StringBuilder builder) {
    984                 super.toString(builder);
    985                 builder.append(',').append("ssid=").append(ssid);
    986             }
    987 
    988             @Override
    989             public boolean equals(Object o) {
    990                 return super.equals(o)
    991                         && Objects.equals(((WifiState) o).ssid, ssid);
    992             }
    993         }
    994     }
    995 
    996     // TODO: Move to its own file.
    997     public static class MobileSignalController extends SignalController<
    998             MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
    999         private final TelephonyManager mPhone;
   1000         private final String mNetworkNameDefault;
   1001         private final String mNetworkNameSeparator;
   1002         @VisibleForTesting
   1003         final PhoneStateListener mPhoneStateListener;
   1004         // Save entire info for logging, we only use the id.
   1005         private final SubscriptionInfo mSubscriptionInfo;
   1006 
   1007         // @VisibleForDemoMode
   1008         final SparseArray<MobileIconGroup> mNetworkToIconLookup;
   1009 
   1010         // Since some pieces of the phone state are interdependent we store it locally,
   1011         // this could potentially become part of MobileState for simplification/complication
   1012         // of code.
   1013         private IccCardConstants.State mSimState = IccCardConstants.State.READY;
   1014         private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
   1015         private int mDataState = TelephonyManager.DATA_DISCONNECTED;
   1016         private ServiceState mServiceState;
   1017         private SignalStrength mSignalStrength;
   1018         private MobileIconGroup mDefaultIcons;
   1019         private Config mConfig;
   1020 
   1021         // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
   1022         // need listener lists anymore.
   1023         public MobileSignalController(Context context, Config config, boolean hasMobileData,
   1024                 TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
   1025                 List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
   1026                 SubscriptionInfo info) {
   1027             super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
   1028                     NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
   1029                     networkController);
   1030             mNetworkToIconLookup = new SparseArray<>();
   1031             mConfig = config;
   1032             mPhone = phone;
   1033             mSubscriptionInfo = info;
   1034             mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
   1035             mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
   1036             mNetworkNameDefault = getStringIfExists(
   1037                     com.android.internal.R.string.lockscreen_carrier_default);
   1038 
   1039             mapIconSets();
   1040 
   1041             mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
   1042             mLastState.enabled = mCurrentState.enabled = hasMobileData;
   1043             mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
   1044             // Get initial data sim state.
   1045             updateDataSim();
   1046         }
   1047 
   1048         public void setConfiguration(Config config) {
   1049             mConfig = config;
   1050             mapIconSets();
   1051             updateTelephony();
   1052         }
   1053 
   1054         /**
   1055          * Get (the mobile parts of) the carrier string.
   1056          *
   1057          * @param currentLabel can be used for concatenation, currently just empty
   1058          * @param connected whether the device has connection to the internet at all
   1059          * @param isMobileLabel whether to always return the network or just when data is connected
   1060          */
   1061         public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) {
   1062             if (!mCurrentState.enabled) {
   1063                 return "";
   1064             } else {
   1065                 String mobileLabel = "";
   1066                 // We want to show the carrier name if in service and either:
   1067                 // - We are connected to mobile data, or
   1068                 // - We are not connected to mobile data, as long as the *reason* packets are not
   1069                 //   being routed over that link is that we have better connectivity via wifi.
   1070                 // If data is disconnected for some other reason but wifi (or ethernet/bluetooth)
   1071                 // is connected, we show nothing.
   1072                 // Otherwise (nothing connected) we show "No internet connection".
   1073                 if (mCurrentState.dataConnected) {
   1074                     mobileLabel = mCurrentState.networkName;
   1075                 } else if (connected || mCurrentState.isEmergency) {
   1076                     if (mCurrentState.connected || mCurrentState.isEmergency) {
   1077                         // The isEmergencyOnly test covers the case of a phone with no SIM
   1078                         mobileLabel = mCurrentState.networkName;
   1079                     }
   1080                 } else {
   1081                     mobileLabel = mContext.getString(
   1082                             R.string.status_bar_settings_signal_meter_disconnected);
   1083                 }
   1084 
   1085                 if (currentLabel.length() != 0) {
   1086                     currentLabel = currentLabel + mNetworkNameSeparator;
   1087                 }
   1088                 // Now for things that should only be shown when actually using mobile data.
   1089                 if (isMobileLabel) {
   1090                     return currentLabel + mobileLabel;
   1091                 } else {
   1092                     return currentLabel
   1093                             + (mCurrentState.dataConnected ? mobileLabel : currentLabel);
   1094                 }
   1095             }
   1096         }
   1097 
   1098         public int getDataContentDescription() {
   1099             return getIcons().mDataContentDescription;
   1100         }
   1101 
   1102         public void setAirplaneMode(boolean airplaneMode) {
   1103             mCurrentState.airplaneMode = airplaneMode;
   1104             notifyListenersIfNecessary();
   1105         }
   1106 
   1107         public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
   1108             // For mobile data, use general inet condition for phone signal indexing,
   1109             // and network specific for data indexing (I think this might be a bug, but
   1110             // keeping for now).
   1111             // TODO: Update with explanation of why.
   1112             mCurrentState.inetForNetwork = inetConditionForNetwork;
   1113             setInetCondition(inetCondition);
   1114         }
   1115 
   1116         /**
   1117          * Start listening for phone state changes.
   1118          */
   1119         public void registerListener() {
   1120             mPhone.listen(mPhoneStateListener,
   1121                     PhoneStateListener.LISTEN_SERVICE_STATE
   1122                             | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
   1123                             | PhoneStateListener.LISTEN_CALL_STATE
   1124                             | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
   1125                             | PhoneStateListener.LISTEN_DATA_ACTIVITY);
   1126         }
   1127 
   1128         /**
   1129          * Stop listening for phone state changes.
   1130          */
   1131         public void unregisterListener() {
   1132             mPhone.listen(mPhoneStateListener, 0);
   1133         }
   1134 
   1135         /**
   1136          * Produce a mapping of data network types to icon groups for simple and quick use in
   1137          * updateTelephony.
   1138          */
   1139         private void mapIconSets() {
   1140             mNetworkToIconLookup.clear();
   1141 
   1142             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
   1143             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
   1144             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
   1145             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
   1146             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
   1147 
   1148             if (!mConfig.showAtLeast3G) {
   1149                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
   1150                         TelephonyIcons.UNKNOWN);
   1151                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
   1152                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
   1153                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
   1154 
   1155                 mDefaultIcons = TelephonyIcons.G;
   1156             } else {
   1157                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
   1158                         TelephonyIcons.THREE_G);
   1159                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
   1160                         TelephonyIcons.THREE_G);
   1161                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
   1162                         TelephonyIcons.THREE_G);
   1163                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
   1164                         TelephonyIcons.THREE_G);
   1165                 mDefaultIcons = TelephonyIcons.THREE_G;
   1166             }
   1167 
   1168             MobileIconGroup hGroup = TelephonyIcons.THREE_G;
   1169             if (mConfig.hspaDataDistinguishable) {
   1170                 hGroup = TelephonyIcons.H;
   1171             }
   1172             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
   1173             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
   1174             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
   1175             mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
   1176 
   1177             if (mConfig.show4gForLte) {
   1178                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
   1179             } else {
   1180                 mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
   1181             }
   1182         }
   1183 
   1184         @Override
   1185         public void notifyListeners() {
   1186             MobileIconGroup icons = getIcons();
   1187 
   1188             String contentDescription = getStringIfExists(getContentDescription());
   1189             String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
   1190 
   1191             boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
   1192                     || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
   1193 
   1194             // Only send data sim callbacks to QS.
   1195             if (mCurrentState.dataSim) {
   1196                 int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
   1197                 int length = mSignalsChangedCallbacks.size();
   1198                 for (int i = 0; i < length; i++) {
   1199                     mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
   1200                             && !mCurrentState.isEmergency,
   1201                             getQsCurrentIconId(), contentDescription,
   1202                             qsTypeIcon,
   1203                             mCurrentState.dataConnected && mCurrentState.activityIn,
   1204                             mCurrentState.dataConnected && mCurrentState.activityOut,
   1205                             dataContentDescription,
   1206                             mCurrentState.isEmergency ? null : mCurrentState.networkName,
   1207                             // Only wide if actually showing something.
   1208                             icons.mIsWide && qsTypeIcon != 0);
   1209                 }
   1210             }
   1211             int typeIcon = showDataIcon ? icons.mDataType : 0;
   1212             int signalClustersLength = mSignalClusters.size();
   1213             for (int i = 0; i < signalClustersLength; i++) {
   1214                 mSignalClusters.get(i).setMobileDataIndicators(
   1215                         mCurrentState.enabled && !mCurrentState.airplaneMode,
   1216                         getCurrentIconId(),
   1217                         typeIcon,
   1218                         contentDescription,
   1219                         dataContentDescription,
   1220                         // Only wide if actually showing something.
   1221                         icons.mIsWide && typeIcon != 0,
   1222                         mSubscriptionInfo.getSubscriptionId());
   1223             }
   1224         }
   1225 
   1226         @Override
   1227         protected MobileState cleanState() {
   1228             return new MobileState();
   1229         }
   1230 
   1231         private boolean hasService() {
   1232             if (mServiceState != null) {
   1233                 // Consider the device to be in service if either voice or data
   1234                 // service is available. Some SIM cards are marketed as data-only
   1235                 // and do not support voice service, and on these SIM cards, we
   1236                 // want to show signal bars for data service as well as the "no
   1237                 // service" or "emergency calls only" text that indicates that voice
   1238                 // is not available.
   1239                 switch (mServiceState.getVoiceRegState()) {
   1240                     case ServiceState.STATE_POWER_OFF:
   1241                         return false;
   1242                     case ServiceState.STATE_OUT_OF_SERVICE:
   1243                     case ServiceState.STATE_EMERGENCY_ONLY:
   1244                         return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
   1245                     default:
   1246                         return true;
   1247                 }
   1248             } else {
   1249                 return false;
   1250             }
   1251         }
   1252 
   1253         private boolean isCdma() {
   1254             return (mSignalStrength != null) && !mSignalStrength.isGsm();
   1255         }
   1256 
   1257         public boolean isEmergencyOnly() {
   1258             return (mServiceState != null && mServiceState.isEmergencyOnly());
   1259         }
   1260 
   1261         private boolean isRoaming() {
   1262             if (isCdma()) {
   1263                 final int iconMode = mServiceState.getCdmaEriIconMode();
   1264                 return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
   1265                         && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
   1266                             || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
   1267             } else {
   1268                 return mServiceState != null && mServiceState.getRoaming();
   1269             }
   1270         }
   1271 
   1272         public void handleBroadcast(Intent intent) {
   1273             String action = intent.getAction();
   1274             if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
   1275                 updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
   1276                         intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
   1277                         intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
   1278                         intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
   1279                 notifyListenersIfNecessary();
   1280             } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
   1281                 updateDataSim();
   1282             }
   1283         }
   1284 
   1285         private void updateDataSim() {
   1286             int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
   1287             if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
   1288                 mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
   1289             } else {
   1290                 // There doesn't seem to be a data sim selected, however if
   1291                 // there isn't a MobileSignalController with dataSim set, then
   1292                 // QS won't get any callbacks and will be blank.  Instead
   1293                 // lets just assume we are the data sim (which will basically
   1294                 // show one at random) in QS until one is selected.  The user
   1295                 // should pick one soon after, so we shouldn't be in this state
   1296                 // for long.
   1297                 mCurrentState.dataSim = true;
   1298             }
   1299             notifyListenersIfNecessary();
   1300         }
   1301 
   1302         /**
   1303          * Updates the network's name based on incoming spn and plmn.
   1304          */
   1305         void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
   1306             if (CHATTY) {
   1307                 Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
   1308                         + " showPlmn=" + showPlmn + " plmn=" + plmn);
   1309             }
   1310             StringBuilder str = new StringBuilder();
   1311             if (showPlmn && plmn != null) {
   1312                 str.append(plmn);
   1313             }
   1314             if (showSpn && spn != null) {
   1315                 if (str.length() != 0) {
   1316                     str.append(mNetworkNameSeparator);
   1317                 }
   1318                 str.append(spn);
   1319             }
   1320             if (str.length() != 0) {
   1321                 mCurrentState.networkName = str.toString();
   1322             } else {
   1323                 mCurrentState.networkName = mNetworkNameDefault;
   1324             }
   1325         }
   1326 
   1327         /**
   1328          * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
   1329          * mDataState, and mSimState.  It should be called any time one of these is updated.
   1330          * This will call listeners if necessary.
   1331          */
   1332         private final void updateTelephony() {
   1333             if (DEBUG) {
   1334                 Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
   1335                         + " ss=" + mSignalStrength);
   1336             }
   1337             mCurrentState.connected = hasService() && mSignalStrength != null;
   1338             if (mCurrentState.connected) {
   1339                 if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
   1340                     mCurrentState.level = mSignalStrength.getCdmaLevel();
   1341                 } else {
   1342                     mCurrentState.level = mSignalStrength.getLevel();
   1343                 }
   1344             }
   1345             if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
   1346                 mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
   1347             } else {
   1348                 mCurrentState.iconGroup = mDefaultIcons;
   1349             }
   1350             mCurrentState.dataConnected = mCurrentState.connected
   1351                     && mDataState == TelephonyManager.DATA_CONNECTED;
   1352 
   1353             if (isRoaming()) {
   1354                 mCurrentState.iconGroup = TelephonyIcons.ROAMING;
   1355             }
   1356             if (isEmergencyOnly() != mCurrentState.isEmergency) {
   1357                 mCurrentState.isEmergency = isEmergencyOnly();
   1358                 mNetworkController.recalculateEmergency();
   1359             }
   1360             // Fill in the network name if we think we have it.
   1361             if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
   1362                     && mServiceState.getOperatorAlphaShort() != null) {
   1363                 mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
   1364             }
   1365             notifyListenersIfNecessary();
   1366         }
   1367 
   1368         @VisibleForTesting
   1369         void setActivity(int activity) {
   1370             mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
   1371                     || activity == TelephonyManager.DATA_ACTIVITY_IN;
   1372             mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
   1373                     || activity == TelephonyManager.DATA_ACTIVITY_OUT;
   1374             notifyListenersIfNecessary();
   1375         }
   1376 
   1377         @Override
   1378         public void dump(PrintWriter pw) {
   1379             super.dump(pw);
   1380             pw.println("  mSubscription=" + mSubscriptionInfo + ",");
   1381             pw.println("  mServiceState=" + mServiceState + ",");
   1382             pw.println("  mSignalStrength=" + mSignalStrength + ",");
   1383             pw.println("  mDataState=" + mDataState + ",");
   1384             pw.println("  mDataNetType=" + mDataNetType + ",");
   1385         }
   1386 
   1387         class MobilePhoneStateListener extends PhoneStateListener {
   1388             public MobilePhoneStateListener(int subId) {
   1389                 super(subId);
   1390             }
   1391 
   1392             @Override
   1393             public void onSignalStrengthsChanged(SignalStrength signalStrength) {
   1394                 if (DEBUG) {
   1395                     Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
   1396                             ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
   1397                 }
   1398                 mSignalStrength = signalStrength;
   1399                 updateTelephony();
   1400             }
   1401 
   1402             @Override
   1403             public void onServiceStateChanged(ServiceState state) {
   1404                 if (DEBUG) {
   1405                     Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
   1406                             + " dataState=" + state.getDataRegState());
   1407                 }
   1408                 mServiceState = state;
   1409                 updateTelephony();
   1410             }
   1411 
   1412             @Override
   1413             public void onDataConnectionStateChanged(int state, int networkType) {
   1414                 if (DEBUG) {
   1415                     Log.d(mTag, "onDataConnectionStateChanged: state=" + state
   1416                             + " type=" + networkType);
   1417                 }
   1418                 mDataState = state;
   1419                 mDataNetType = networkType;
   1420                 updateTelephony();
   1421             }
   1422 
   1423             @Override
   1424             public void onDataActivity(int direction) {
   1425                 if (DEBUG) {
   1426                     Log.d(mTag, "onDataActivity: direction=" + direction);
   1427                 }
   1428                 setActivity(direction);
   1429             }
   1430         };
   1431 
   1432         static class MobileIconGroup extends SignalController.IconGroup {
   1433             final int mDataContentDescription; // mContentDescriptionDataType
   1434             final int mDataType;
   1435             final boolean mIsWide;
   1436             final int[] mQsDataType;
   1437 
   1438             public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
   1439                     int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
   1440                     int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
   1441                     int[] qsDataType) {
   1442                 super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
   1443                         qsDiscState, discContentDesc);
   1444                 mDataContentDescription = dataContentDesc;
   1445                 mDataType = dataType;
   1446                 mIsWide = isWide;
   1447                 mQsDataType = qsDataType;
   1448             }
   1449         }
   1450 
   1451         static class MobileState extends SignalController.State {
   1452             String networkName;
   1453             boolean dataSim;
   1454             boolean dataConnected;
   1455             boolean isEmergency;
   1456             boolean airplaneMode;
   1457             int inetForNetwork;
   1458 
   1459             @Override
   1460             public void copyFrom(State s) {
   1461                 super.copyFrom(s);
   1462                 MobileState state = (MobileState) s;
   1463                 dataSim = state.dataSim;
   1464                 networkName = state.networkName;
   1465                 dataConnected = state.dataConnected;
   1466                 inetForNetwork = state.inetForNetwork;
   1467                 isEmergency = state.isEmergency;
   1468                 airplaneMode = state.airplaneMode;
   1469             }
   1470 
   1471             @Override
   1472             protected void toString(StringBuilder builder) {
   1473                 super.toString(builder);
   1474                 builder.append(',');
   1475                 builder.append("dataSim=").append(dataSim).append(',');
   1476                 builder.append("networkName=").append(networkName).append(',');
   1477                 builder.append("dataConnected=").append(dataConnected).append(',');
   1478                 builder.append("inetForNetwork=").append(inetForNetwork).append(',');
   1479                 builder.append("isEmergency=").append(isEmergency).append(',');
   1480                 builder.append("airplaneMode=").append(airplaneMode);
   1481             }
   1482 
   1483             @Override
   1484             public boolean equals(Object o) {
   1485                 return super.equals(o)
   1486                         && Objects.equals(((MobileState) o).networkName, networkName)
   1487                         && ((MobileState) o).dataSim == dataSim
   1488                         && ((MobileState) o).dataConnected == dataConnected
   1489                         && ((MobileState) o).isEmergency == isEmergency
   1490                         && ((MobileState) o).airplaneMode == airplaneMode
   1491                         && ((MobileState) o).inetForNetwork == inetForNetwork;
   1492             }
   1493         }
   1494     }
   1495 
   1496     /**
   1497      * Common base class for handling signal for both wifi and mobile data.
   1498      */
   1499     static abstract class SignalController<T extends SignalController.State,
   1500             I extends SignalController.IconGroup> {
   1501         protected final String mTag;
   1502         protected final T mCurrentState;
   1503         protected final T mLastState;
   1504         protected final int mTransportType;
   1505         protected final Context mContext;
   1506         // The owner of the SignalController (i.e. NetworkController will maintain the following
   1507         // lists and call notifyListeners whenever the list has changed to ensure everyone
   1508         // is aware of current state.
   1509         protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
   1510         protected final List<SignalCluster> mSignalClusters;
   1511         protected final NetworkControllerImpl mNetworkController;
   1512 
   1513         // Save the previous HISTORY_SIZE states for logging.
   1514         private final State[] mHistory;
   1515         // Where to copy the next state into.
   1516         private int mHistoryIndex;
   1517 
   1518         public SignalController(String tag, Context context, int type,
   1519                 List<NetworkSignalChangedCallback> signalCallbacks,
   1520                 List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
   1521             mTag = TAG + "." + tag;
   1522             mNetworkController = networkController;
   1523             mTransportType = type;
   1524             mContext = context;
   1525             mSignalsChangedCallbacks = signalCallbacks;
   1526             mSignalClusters = signalClusters;
   1527             mCurrentState = cleanState();
   1528             mLastState = cleanState();
   1529             if (RECORD_HISTORY) {
   1530                 mHistory = new State[HISTORY_SIZE];
   1531                 for (int i = 0; i < HISTORY_SIZE; i++) {
   1532                     mHistory[i] = cleanState();
   1533                 }
   1534             }
   1535         }
   1536 
   1537         public T getState() {
   1538             return mCurrentState;
   1539         }
   1540 
   1541         public int getTransportType() {
   1542             return mTransportType;
   1543         }
   1544 
   1545         public void setInetCondition(int inetCondition) {
   1546             mCurrentState.inetCondition = inetCondition;
   1547             notifyListenersIfNecessary();
   1548         }
   1549 
   1550         /**
   1551          * Used at the end of demo mode to clear out any ugly state that it has created.
   1552          * Since we haven't had any callbacks, then isDirty will not have been triggered,
   1553          * so we can just take the last good state directly from there.
   1554          *
   1555          * Used for demo mode.
   1556          */
   1557         void resetLastState() {
   1558             mCurrentState.copyFrom(mLastState);
   1559         }
   1560 
   1561         /**
   1562          * Determines if the state of this signal controller has changed and
   1563          * needs to trigger callbacks related to it.
   1564          */
   1565         public boolean isDirty() {
   1566             if (!mLastState.equals(mCurrentState)) {
   1567                 if (DEBUG) {
   1568                     Log.d(mTag, "Change in state from: " + mLastState + "\n"
   1569                             + "\tto: " + mCurrentState);
   1570                 }
   1571                 return true;
   1572             }
   1573             return false;
   1574         }
   1575 
   1576         public void saveLastState() {
   1577             if (RECORD_HISTORY) {
   1578                 recordLastState();
   1579             }
   1580             // Updates the current time.
   1581             mCurrentState.time = System.currentTimeMillis();
   1582             mLastState.copyFrom(mCurrentState);
   1583         }
   1584 
   1585         /**
   1586          * Gets the signal icon for QS based on current state of connected, enabled, and level.
   1587          */
   1588         public int getQsCurrentIconId() {
   1589             if (mCurrentState.connected) {
   1590                 return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
   1591             } else if (mCurrentState.enabled) {
   1592                 return getIcons().mQsDiscState;
   1593             } else {
   1594                 return getIcons().mQsNullState;
   1595             }
   1596         }
   1597 
   1598         /**
   1599          * Gets the signal icon for SB based on current state of connected, enabled, and level.
   1600          */
   1601         public int getCurrentIconId() {
   1602             if (mCurrentState.connected) {
   1603                 return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
   1604             } else if (mCurrentState.enabled) {
   1605                 return getIcons().mSbDiscState;
   1606             } else {
   1607                 return getIcons().mSbNullState;
   1608             }
   1609         }
   1610 
   1611         /**
   1612          * Gets the content description id for the signal based on current state of connected and
   1613          * level.
   1614          */
   1615         public int getContentDescription() {
   1616             if (mCurrentState.connected) {
   1617                 return getIcons().mContentDesc[mCurrentState.level];
   1618             } else {
   1619                 return getIcons().mDiscContentDesc;
   1620             }
   1621         }
   1622 
   1623         public void notifyListenersIfNecessary() {
   1624             if (isDirty()) {
   1625                 saveLastState();
   1626                 notifyListeners();
   1627                 mNetworkController.refreshCarrierLabel();
   1628             }
   1629         }
   1630 
   1631         /**
   1632          * Returns the resource if resId is not 0, and an empty string otherwise.
   1633          */
   1634         protected String getStringIfExists(int resId) {
   1635             return resId != 0 ? mContext.getString(resId) : "";
   1636         }
   1637 
   1638         protected I getIcons() {
   1639             return (I) mCurrentState.iconGroup;
   1640         }
   1641 
   1642         /**
   1643          * Saves the last state of any changes, so we can log the current
   1644          * and last value of any state data.
   1645          */
   1646         protected void recordLastState() {
   1647             mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
   1648         }
   1649 
   1650         public void dump(PrintWriter pw) {
   1651             pw.println("  - " + mTag + " -----");
   1652             pw.println("  Current State: " + mCurrentState);
   1653             if (RECORD_HISTORY) {
   1654                 // Count up the states that actually contain time stamps, and only display those.
   1655                 int size = 0;
   1656                 for (int i = 0; i < HISTORY_SIZE; i++) {
   1657                     if (mHistory[i].time != 0) size++;
   1658                 }
   1659                 // Print out the previous states in ordered number.
   1660                 for (int i = mHistoryIndex + HISTORY_SIZE - 1;
   1661                         i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
   1662                     pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": "
   1663                             + mHistory[i & (HISTORY_SIZE - 1)]);
   1664                 }
   1665             }
   1666         }
   1667 
   1668         /**
   1669          * Trigger callbacks based on current state.  The callbacks should be completely
   1670          * based on current state, and only need to be called in the scenario where
   1671          * mCurrentState != mLastState.
   1672          */
   1673         public abstract void notifyListeners();
   1674 
   1675         /**
   1676          * Generate a blank T.
   1677          */
   1678         protected abstract T cleanState();
   1679 
   1680         /*
   1681          * Holds icons for a given state. Arrays are generally indexed as inet
   1682          * state (full connectivity or not) first, and second dimension as
   1683          * signal strength.
   1684          */
   1685         static class IconGroup {
   1686             final int[][] mSbIcons;
   1687             final int[][] mQsIcons;
   1688             final int[] mContentDesc;
   1689             final int mSbNullState;
   1690             final int mQsNullState;
   1691             final int mSbDiscState;
   1692             final int mQsDiscState;
   1693             final int mDiscContentDesc;
   1694             // For logging.
   1695             final String mName;
   1696 
   1697             public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
   1698                     int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
   1699                     int discContentDesc) {
   1700                 mName = name;
   1701                 mSbIcons = sbIcons;
   1702                 mQsIcons = qsIcons;
   1703                 mContentDesc = contentDesc;
   1704                 mSbNullState = sbNullState;
   1705                 mQsNullState = qsNullState;
   1706                 mSbDiscState = sbDiscState;
   1707                 mQsDiscState = qsDiscState;
   1708                 mDiscContentDesc = discContentDesc;
   1709             }
   1710 
   1711             @Override
   1712             public String toString() {
   1713                 return "IconGroup(" + mName + ")";
   1714             }
   1715         }
   1716 
   1717         static class State {
   1718             boolean connected;
   1719             boolean enabled;
   1720             boolean activityIn;
   1721             boolean activityOut;
   1722             int level;
   1723             IconGroup iconGroup;
   1724             int inetCondition;
   1725             int rssi; // Only for logging.
   1726 
   1727             // Not used for comparison, just used for logging.
   1728             long time;
   1729 
   1730             public void copyFrom(State state) {
   1731                 connected = state.connected;
   1732                 enabled = state.enabled;
   1733                 level = state.level;
   1734                 iconGroup = state.iconGroup;
   1735                 inetCondition = state.inetCondition;
   1736                 activityIn = state.activityIn;
   1737                 activityOut = state.activityOut;
   1738                 rssi = state.rssi;
   1739                 time = state.time;
   1740             }
   1741 
   1742             @Override
   1743             public String toString() {
   1744                 if (time != 0) {
   1745                     StringBuilder builder = new StringBuilder();
   1746                     toString(builder);
   1747                     return builder.toString();
   1748                 } else {
   1749                     return "Empty " + getClass().getSimpleName();
   1750                 }
   1751             }
   1752 
   1753             protected void toString(StringBuilder builder) {
   1754                 builder.append("connected=").append(connected).append(',')
   1755                         .append("enabled=").append(enabled).append(',')
   1756                         .append("level=").append(level).append(',')
   1757                         .append("inetCondition=").append(inetCondition).append(',')
   1758                         .append("iconGroup=").append(iconGroup).append(',')
   1759                         .append("activityIn=").append(activityIn).append(',')
   1760                         .append("activityOut=").append(activityOut).append(',')
   1761                         .append("rssi=").append(rssi).append(',')
   1762                         .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
   1763             }
   1764 
   1765             @Override
   1766             public boolean equals(Object o) {
   1767                 if (!o.getClass().equals(getClass())) {
   1768                     return false;
   1769                 }
   1770                 State other = (State) o;
   1771                 return other.connected == connected
   1772                         && other.enabled == enabled
   1773                         && other.level == level
   1774                         && other.inetCondition == inetCondition
   1775                         && other.iconGroup == iconGroup
   1776                         && other.activityIn == activityIn
   1777                         && other.activityOut == activityOut
   1778                         && other.rssi == rssi;
   1779             }
   1780         }
   1781     }
   1782 
   1783     public interface SignalCluster {
   1784         void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
   1785 
   1786         void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
   1787                 String contentDescription, String typeContentDescription, boolean isTypeIconWide,
   1788                 int subId);
   1789         void setSubs(List<SubscriptionInfo> subs);
   1790         void setNoSims(boolean show);
   1791 
   1792         void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
   1793     }
   1794 
   1795     public interface EmergencyListener {
   1796         void setEmergencyCallsOnly(boolean emergencyOnly);
   1797     }
   1798 
   1799     public interface CarrierLabelListener {
   1800         void setCarrierLabel(String label);
   1801     }
   1802 
   1803     @VisibleForTesting
   1804     static class Config {
   1805         boolean showAtLeast3G = false;
   1806         boolean alwaysShowCdmaRssi = false;
   1807         boolean show4gForLte = false;
   1808         boolean hspaDataDistinguishable;
   1809 
   1810         static Config readConfig(Context context) {
   1811             Config config = new Config();
   1812             Resources res = context.getResources();
   1813 
   1814             config.showAtLeast3G = res.getBoolean(R.bool.config_showMin3G);
   1815             config.alwaysShowCdmaRssi =
   1816                     res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
   1817             config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
   1818             config.hspaDataDistinguishable =
   1819                     res.getBoolean(R.bool.config_hspa_data_distinguishable);
   1820             return config;
   1821         }
   1822     }
   1823 }
   1824