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