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