NetworkControllerImpl.java revision 0e2400fb16f5a52f755d8a2dc2a4688cf0c9a247
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 android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.Resources;
24import android.net.ConnectivityManager;
25import android.net.NetworkInfo;
26import android.net.wifi.WifiConfiguration;
27import android.net.wifi.WifiInfo;
28import android.net.wifi.WifiManager;
29import android.os.AsyncTask;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Message;
33import android.os.Messenger;
34import android.provider.Settings;
35import android.telephony.PhoneStateListener;
36import android.telephony.ServiceState;
37import android.telephony.SignalStrength;
38import android.telephony.TelephonyManager;
39import android.text.format.DateFormat;
40import android.util.Log;
41
42import com.android.internal.annotations.VisibleForTesting;
43import com.android.internal.telephony.IccCardConstants;
44import com.android.internal.telephony.TelephonyIntents;
45import com.android.internal.telephony.cdma.EriInfo;
46import com.android.internal.util.AsyncChannel;
47import com.android.systemui.DemoMode;
48import com.android.systemui.R;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52import java.util.ArrayList;
53import java.util.HashMap;
54import java.util.List;
55import java.util.Locale;
56import java.util.Map;
57import java.util.Objects;
58
59/** Platform implementation of the network controller. **/
60public class NetworkControllerImpl extends BroadcastReceiver
61        implements NetworkController, DemoMode {
62    // debug
63    static final String TAG = "NetworkController";
64    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
65    // additional diagnostics, but not logspew
66    static final boolean CHATTY =  Log.isLoggable(TAG + ".Chat", Log.DEBUG);
67    // Save the previous states of all SignalController state info.
68    static final boolean RECORD_HISTORY = true;
69    // How many to save, must be a power of 2.
70    static final int HISTORY_SIZE = 16;
71
72    private static final int INET_CONDITION_THRESHOLD = 50;
73
74    private final Context mContext;
75    private final TelephonyManager mPhone;
76    private final WifiManager mWifiManager;
77    private final ConnectivityManager mConnectivityManager;
78    private final boolean mHasMobileDataFeature;
79
80    // Subcontrollers.
81    @VisibleForTesting
82    final WifiSignalController mWifiSignalController;
83    @VisibleForTesting
84    final MobileSignalController mMobileSignalController;
85    private final AccessPointControllerImpl mAccessPoints;
86    private final MobileDataControllerImpl mMobileDataController;
87
88    // bluetooth
89    private boolean mBluetoothTethered = false;
90
91    // data connectivity (regardless of state, can we access the internet?)
92    // state of inet connection - 0 not connected, 100 connected
93    private boolean mConnected = false;
94    private int mConnectedNetworkType = ConnectivityManager.TYPE_NONE;
95    private String mConnectedNetworkTypeName;
96    private boolean mInetCondition; // Used for Logging and demo.
97
98    // States that don't belong to a subcontroller.
99    private boolean mAirplaneMode = false;
100    private Locale mLocale = null;
101
102    // All the callbacks.
103    private ArrayList<EmergencyListener> mEmergencyListeners = new ArrayList<EmergencyListener>();
104    private ArrayList<CarrierLabelListener> mCarrierListeners =
105            new ArrayList<CarrierLabelListener>();
106    private ArrayList<SignalCluster> mSignalClusters = new ArrayList<SignalCluster>();
107    private ArrayList<NetworkSignalChangedCallback> mSignalsChangedCallbacks =
108            new ArrayList<NetworkSignalChangedCallback>();
109
110    /**
111     * Construct this controller object and register for updates.
112     */
113    public NetworkControllerImpl(Context context) {
114        this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
115                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
116                (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
117                Config.readConfig(context), new AccessPointControllerImpl(context),
118                new MobileDataControllerImpl(context));
119        registerListeners();
120    }
121
122    @VisibleForTesting
123    NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
124            TelephonyManager telephonyManager, WifiManager wifiManager, Config config,
125            AccessPointControllerImpl accessPointController,
126            MobileDataControllerImpl mobileDataController) {
127        mContext = context;
128
129        mConnectivityManager = connectivityManager;
130        mHasMobileDataFeature =
131                mConnectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
132
133        // telephony
134        mPhone = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
135
136        // wifi
137        mWifiManager = wifiManager;
138
139        mLocale = mContext.getResources().getConfiguration().locale;
140        mAccessPoints = accessPointController;
141        mMobileDataController = mobileDataController;
142        mMobileDataController.setNetworkController(this);
143        // TODO: Find a way to move this into MobileDataController.
144        mMobileDataController.setCallback(new MobileDataControllerImpl.Callback() {
145            @Override
146            public void onMobileDataEnabled(boolean enabled) {
147                notifyMobileDataEnabled(enabled);
148            }
149        });
150        mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
151                mSignalsChangedCallbacks, mSignalClusters, this);
152        mMobileSignalController = new MobileSignalController(mContext, config,
153                mHasMobileDataFeature, mPhone, mSignalsChangedCallbacks, mSignalClusters, this);
154
155        // AIRPLANE_MODE_CHANGED is sent at boot; we've probably already missed it
156        updateAirplaneMode(true);
157        mAccessPoints.setNetworkController(this);
158    }
159
160    private void registerListeners() {
161        mMobileSignalController.registerListener();
162
163        // broadcasts
164        IntentFilter filter = new IntentFilter();
165        filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
166        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
167        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
168        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
169        filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
170        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE);
171        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
172        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
173        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
174        mContext.registerReceiver(this, filter);
175    }
176
177    private void unregisterListeners() {
178        mMobileSignalController.unregisterListener();
179        mContext.unregisterReceiver(this);
180    }
181
182    public int getConnectedWifiLevel() {
183        return mWifiSignalController.getState().level;
184    }
185
186    @Override
187    public AccessPointController getAccessPointController() {
188        return mAccessPoints;
189    }
190
191    @Override
192    public MobileDataController getMobileDataController() {
193        return mMobileDataController;
194    }
195
196    public void addEmergencyListener(EmergencyListener listener) {
197        mEmergencyListeners.add(listener);
198        refreshCarrierLabel();
199    }
200
201    public void addCarrierLabel(CarrierLabelListener listener) {
202        mCarrierListeners.add(listener);
203        refreshCarrierLabel();
204    }
205
206    private void notifyMobileDataEnabled(boolean enabled) {
207        int length = mSignalsChangedCallbacks.size();
208        for (int i = 0; i < length; i++) {
209            mSignalsChangedCallbacks.get(i).onMobileDataEnabled(enabled);
210        }
211    }
212
213    public boolean hasMobileDataFeature() {
214        return mHasMobileDataFeature;
215    }
216
217    public boolean hasVoiceCallingFeature() {
218        return mPhone.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
219    }
220
221    public String getMobileNetworkName() {
222        return mMobileSignalController.mCurrentState.networkName;
223    }
224
225    public boolean isEmergencyOnly() {
226        return mMobileSignalController.isEmergencyOnly();
227    }
228
229    /**
230     * Emergency status may have changed (triggered by MobileSignalController),
231     * so we should recheck and send out the state to listeners.
232     */
233    void recalculateEmergency() {
234        final boolean emergencyOnly = isEmergencyOnly();
235
236        int length = mEmergencyListeners.size();
237        for (int i = 0; i < length; i++) {
238            mEmergencyListeners.get(i).setEmergencyCallsOnly(emergencyOnly);
239        }
240    }
241
242    public void addSignalCluster(SignalCluster cluster) {
243        mSignalClusters.add(cluster);
244        cluster.setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
245                R.string.accessibility_airplane_mode);
246        mWifiSignalController.notifyListeners();
247        mMobileSignalController.notifyListeners();
248    }
249
250    public void addNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
251        mSignalsChangedCallbacks.add(cb);
252        cb.onAirplaneModeChanged(mAirplaneMode);
253        mWifiSignalController.notifyListeners();
254        mMobileSignalController.notifyListeners();
255    }
256
257    public void removeNetworkSignalChangedCallback(NetworkSignalChangedCallback cb) {
258        mSignalsChangedCallbacks.remove(cb);
259    }
260
261    @Override
262    public void setWifiEnabled(final boolean enabled) {
263        new AsyncTask<Void, Void, Void>() {
264            @Override
265            protected Void doInBackground(Void... args) {
266                // Disable tethering if enabling Wifi
267                final int wifiApState = mWifiManager.getWifiApState();
268                if (enabled && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
269                        (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
270                    mWifiManager.setWifiApEnabled(null, false);
271                }
272
273                mWifiManager.setWifiEnabled(enabled);
274                return null;
275            }
276        }.execute();
277    }
278
279    @Override
280    public void onReceive(Context context, Intent intent) {
281        if (CHATTY) {
282            Log.d(TAG, "onReceive: intent=" + intent);
283        }
284        final String action = intent.getAction();
285        if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE) ||
286                action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
287            updateConnectivity(intent);
288            refreshCarrierLabel();
289        } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
290            refreshLocale();
291            refreshCarrierLabel();
292        } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
293            refreshLocale();
294            updateAirplaneMode(false);
295            refreshCarrierLabel();
296        }
297        mWifiSignalController.handleBroadcast(intent);
298        mMobileSignalController.handleBroadcast(intent);
299    }
300
301    private void updateAirplaneMode(boolean force) {
302        boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
303                Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
304        if (airplaneMode != mAirplaneMode || force) {
305            mAirplaneMode = airplaneMode;
306            mMobileSignalController.setAirplaneMode(mAirplaneMode);
307            notifyAirplaneCallbacks();
308            refreshCarrierLabel();
309        }
310    }
311
312    private void refreshLocale() {
313        Locale current = mContext.getResources().getConfiguration().locale;
314        if (current.equals(mLocale)) {
315            mLocale = current;
316            notifyAllListeners();
317        }
318    }
319
320    /**
321     * Turns inet condition into a boolean indexing for a specific network.
322     * returns 0 for bad connectivity on this network.
323     * returns 1 for good connectivity on this network.
324     */
325    private int inetConditionForNetwork(int networkType, boolean inetCondition) {
326        return (inetCondition && mConnectedNetworkType == networkType) ? 1 : 0;
327    }
328
329    private void notifyAllListeners() {
330        // Something changed, trigger everything!
331        notifyAirplaneCallbacks();
332        mMobileSignalController.notifyListeners();
333        mWifiSignalController.notifyListeners();
334    }
335
336    private void notifyAirplaneCallbacks() {
337        int length = mSignalClusters.size();
338        for (int i = 0; i < length; i++) {
339            mSignalClusters.get(i).setIsAirplaneMode(mAirplaneMode, TelephonyIcons.FLIGHT_MODE_ICON,
340                    R.string.accessibility_airplane_mode);
341        }
342        // update QS
343        int signalsChangedLength = mSignalsChangedCallbacks.size();
344        for (int i = 0; i < signalsChangedLength; i++) {
345            mSignalsChangedCallbacks.get(i).onAirplaneModeChanged(mAirplaneMode);
346        }
347    }
348
349    /**
350     * Update the Inet conditions and what network we are connected to.
351     */
352    private void updateConnectivity(Intent intent) {
353        final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
354
355        // Are we connected at all, by any interface?
356        mConnected = info != null && info.isConnected();
357        if (mConnected) {
358            mConnectedNetworkType = info.getType();
359            mConnectedNetworkTypeName = info.getTypeName();
360        } else {
361            mConnectedNetworkType = ConnectivityManager.TYPE_NONE;
362            mConnectedNetworkTypeName = null;
363        }
364
365        int connectionStatus = intent.getIntExtra(ConnectivityManager.EXTRA_INET_CONDITION, 0);
366
367        if (CHATTY) {
368            Log.d(TAG, "updateConnectivity: networkInfo=" + info);
369            Log.d(TAG, "updateConnectivity: connectionStatus=" + connectionStatus);
370        }
371
372        mInetCondition = connectionStatus > INET_CONDITION_THRESHOLD;
373
374        if (info != null && info.getType() == ConnectivityManager.TYPE_BLUETOOTH) {
375            mBluetoothTethered = info.isConnected();
376        } else {
377            mBluetoothTethered = false;
378        }
379
380        // We want to update all the icons, all at once, for any condition change
381        mMobileSignalController.setInetCondition(mInetCondition ? 1 : 0,
382                inetConditionForNetwork(mMobileSignalController.getNetworkType(), mInetCondition));
383        mWifiSignalController.setInetCondition(
384                inetConditionForNetwork(mWifiSignalController.getNetworkType(), mInetCondition));
385    }
386
387    /**
388     * Recalculate and update the carrier label.
389     */
390    void refreshCarrierLabel() {
391        Context context = mContext;
392
393        WifiSignalController.WifiState wifiState = mWifiSignalController.getState();
394        MobileSignalController.MobileState mobileState = mMobileSignalController.getState();
395        String label = mMobileSignalController.getLabel("", mConnected, mHasMobileDataFeature);
396
397        // TODO Simplify this ugliness, some of the flows below shouldn't be possible anymore
398        // but stay for the sake of history.
399        if (mBluetoothTethered && !mHasMobileDataFeature) {
400            label = mContext.getString(R.string.bluetooth_tethered);
401        }
402
403        final boolean ethernetConnected =
404                (mConnectedNetworkType == ConnectivityManager.TYPE_ETHERNET);
405        if (ethernetConnected && !mHasMobileDataFeature) {
406            label = context.getString(R.string.ethernet_label);
407        }
408
409        if (mAirplaneMode && (!mobileState.connected && !mobileState.isEmergency)) {
410            // combined values from connected wifi take precedence over airplane mode
411            if (wifiState.connected && mHasMobileDataFeature) {
412                // Suppress "No internet connection." from mobile if wifi connected.
413                label = "";
414            } else {
415                 if (!mHasMobileDataFeature) {
416                      label = context.getString(
417                              R.string.status_bar_settings_signal_meter_disconnected);
418                 }
419            }
420        } else if (!mobileState.dataConnected && !wifiState.connected && !mBluetoothTethered &&
421                 !ethernetConnected && !mHasMobileDataFeature) {
422            // Pretty much no connection.
423            label = context.getString(R.string.status_bar_settings_signal_meter_disconnected);
424        }
425
426        // for mobile devices, we always show mobile connection info here (SPN/PLMN)
427        // for other devices, we show whatever network is connected
428        // This is determined above by references to mHasMobileDataFeature.
429        int length = mCarrierListeners.size();
430        for (int i = 0; i < length; i++) {
431            mCarrierListeners.get(i).setCarrierLabel(label);
432        }
433    }
434
435    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
436        pw.println("NetworkController state:");
437        pw.println(String.format("  %s network type %d (%s)",
438                mConnected ? "CONNECTED" : "DISCONNECTED",
439                mConnectedNetworkType, mConnectedNetworkTypeName));
440        pw.println("  - telephony ------");
441        pw.print("  hasVoiceCallingFeature()=");
442        pw.println(hasVoiceCallingFeature());
443
444        pw.println("  - Bluetooth ----");
445        pw.print("  mBtReverseTethered=");
446        pw.println(mBluetoothTethered);
447
448        pw.println("  - connectivity ------");
449        pw.print("  mInetCondition=");
450        pw.println(mInetCondition);
451        pw.print("  mAirplaneMode=");
452        pw.println(mAirplaneMode);
453        pw.print("  mLocale=");
454        pw.println(mLocale);
455
456        mMobileSignalController.dump(pw);
457        mWifiSignalController.dump(pw);
458    }
459
460    private boolean mDemoMode;
461    private int mDemoInetCondition;
462    private WifiSignalController.WifiState mDemoWifiState;
463    private MobileSignalController.MobileState mDemoMobileState;
464
465    @Override
466    public void dispatchDemoCommand(String command, Bundle args) {
467        if (!mDemoMode && command.equals(COMMAND_ENTER)) {
468            if (DEBUG) Log.d(TAG, "Entering demo mode");
469            unregisterListeners();
470            mDemoMode = true;
471            mDemoInetCondition = mInetCondition ? 1 : 0;
472            mDemoWifiState = mWifiSignalController.getState();
473            mDemoMobileState = mMobileSignalController.getState();
474        } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
475            if (DEBUG) Log.d(TAG, "Exiting demo mode");
476            mDemoMode = false;
477            mWifiSignalController.resetLastState();
478            mMobileSignalController.resetLastState();
479            registerListeners();
480            notifyAllListeners();
481            refreshCarrierLabel();
482        } else if (mDemoMode && command.equals(COMMAND_NETWORK)) {
483            String airplane = args.getString("airplane");
484            if (airplane != null) {
485                boolean show = airplane.equals("show");
486                int length = mSignalClusters.size();
487                for (int i = 0; i < length; i++) {
488                    mSignalClusters.get(i).setIsAirplaneMode(show, TelephonyIcons.FLIGHT_MODE_ICON,
489                            R.string.accessibility_airplane_mode);
490                }
491            }
492            String fully = args.getString("fully");
493            if (fully != null) {
494                mDemoInetCondition = Boolean.parseBoolean(fully) ? 1 : 0;
495                mWifiSignalController.setInetCondition(mDemoInetCondition);
496                mMobileSignalController.setInetCondition(mDemoInetCondition, mDemoInetCondition);
497            }
498            String wifi = args.getString("wifi");
499            if (wifi != null) {
500                boolean show = wifi.equals("show");
501                String level = args.getString("level");
502                if (level != null) {
503                    mDemoWifiState.level = level.equals("null") ? -1
504                            : Math.min(Integer.parseInt(level), WifiIcons.WIFI_LEVEL_COUNT - 1);
505                    mDemoWifiState.connected = mDemoWifiState.level >= 0;
506                }
507                mDemoWifiState.enabled = show;
508                mWifiSignalController.notifyListeners();
509            }
510            String mobile = args.getString("mobile");
511            if (mobile != null) {
512                boolean show = mobile.equals("show");
513                String datatype = args.getString("datatype");
514                if (datatype != null) {
515                    mDemoMobileState.iconGroup =
516                            datatype.equals("1x") ? TelephonyIcons.ONE_X :
517                            datatype.equals("3g") ? TelephonyIcons.THREE_G :
518                            datatype.equals("4g") ? TelephonyIcons.FOUR_G :
519                            datatype.equals("e") ? TelephonyIcons.E :
520                            datatype.equals("g") ? TelephonyIcons.G :
521                            datatype.equals("h") ? TelephonyIcons.H :
522                            datatype.equals("lte") ? TelephonyIcons.LTE :
523                            datatype.equals("roam") ? TelephonyIcons.ROAMING :
524                            TelephonyIcons.UNKNOWN;
525                }
526                int[][] icons = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH;
527                String level = args.getString("level");
528                if (level != null) {
529                    mDemoMobileState.level = level.equals("null") ? -1
530                            : Math.min(Integer.parseInt(level), icons[0].length - 1);
531                    mDemoMobileState.connected = mDemoMobileState.level >= 0;
532                }
533                mDemoMobileState.enabled = show;
534                mMobileSignalController.notifyListeners();
535            }
536            refreshCarrierLabel();
537        }
538    }
539
540    static class WifiSignalController extends
541            SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
542        private final WifiManager mWifiManager;
543        private final AsyncChannel mWifiChannel;
544        private final boolean mHasMobileData;
545
546        public WifiSignalController(Context context, boolean hasMobileData,
547                List<NetworkSignalChangedCallback> signalCallbacks,
548                List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
549            super("WifiSignalController", context, ConnectivityManager.TYPE_WIFI, signalCallbacks,
550                    signalClusters, networkController);
551            mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
552            mHasMobileData = hasMobileData;
553            Handler handler = new WifiHandler();
554            mWifiChannel = new AsyncChannel();
555            Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
556            if (wifiMessenger != null) {
557                mWifiChannel.connect(context, handler, wifiMessenger);
558            }
559            // WiFi only has one state.
560            mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
561                    "Wi-Fi Icons",
562                    WifiIcons.WIFI_SIGNAL_STRENGTH,
563                    WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
564                    AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
565                    WifiIcons.WIFI_NO_NETWORK,
566                    WifiIcons.QS_WIFI_NO_NETWORK,
567                    WifiIcons.WIFI_NO_NETWORK,
568                    WifiIcons.QS_WIFI_NO_NETWORK,
569                    AccessibilityContentDescriptions.WIFI_NO_CONNECTION
570                    );
571        }
572
573        @Override
574        public WifiState cleanState() {
575            return new WifiState();
576        }
577
578        /**
579         * {@inheritDoc}
580         */
581        @Override
582        public void notifyListeners() {
583            // only show wifi in the cluster if connected or if wifi-only
584            boolean wifiEnabled = mCurrentState.enabled
585                    && (mCurrentState.connected || !mHasMobileData);
586            String wifiDesc = wifiEnabled ? mCurrentState.ssid : null;
587            boolean ssidPresent = wifiEnabled && mCurrentState.ssid != null;
588            String contentDescription = getStringIfExists(getContentDescription());
589            int length = mSignalsChangedCallbacks.size();
590            for (int i = 0; i < length; i++) {
591                mSignalsChangedCallbacks.get(i).onWifiSignalChanged(mCurrentState.enabled,
592                        mCurrentState.connected, getQsCurrentIconId(),
593                        ssidPresent && mCurrentState.activityIn,
594                        ssidPresent && mCurrentState.activityOut, contentDescription, wifiDesc);
595            }
596
597            int signalClustersLength = mSignalClusters.size();
598            for (int i = 0; i < signalClustersLength; i++) {
599                mSignalClusters.get(i).setWifiIndicators(
600                        // only show wifi in the cluster if connected or if wifi-only
601                        mCurrentState.enabled && (mCurrentState.connected || !mHasMobileData),
602                        getCurrentIconId(), contentDescription);
603            }
604        }
605
606        /**
607         * Extract wifi state directly from broadcasts about changes in wifi state.
608         */
609        public void handleBroadcast(Intent intent) {
610            String action = intent.getAction();
611            if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
612                mCurrentState.enabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
613                        WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
614            } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
615                final NetworkInfo networkInfo = (NetworkInfo)
616                        intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
617                mCurrentState.connected = networkInfo != null && networkInfo.isConnected();
618                // If Connected grab the signal strength and ssid.
619                if (mCurrentState.connected) {
620                    // try getting it out of the intent first
621                    WifiInfo info = intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO) != null
622                            ? (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)
623                            : mWifiManager.getConnectionInfo();
624                    if (info != null) {
625                        mCurrentState.ssid = huntForSsid(info);
626                    } else {
627                        mCurrentState.ssid = null;
628                    }
629                } else if (!mCurrentState.connected) {
630                    mCurrentState.ssid = null;
631                }
632            } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
633                mCurrentState.rssi = intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200);
634                mCurrentState.level = WifiManager.calculateSignalLevel(
635                        mCurrentState.rssi, WifiIcons.WIFI_LEVEL_COUNT);
636            }
637
638            notifyListenersIfNecessary();
639        }
640
641        private String huntForSsid(WifiInfo info) {
642            String ssid = info.getSSID();
643            if (ssid != null) {
644                return ssid;
645            }
646            // OK, it's not in the connectionInfo; we have to go hunting for it
647            List<WifiConfiguration> networks = mWifiManager.getConfiguredNetworks();
648            int length = networks.size();
649            for (int i = 0; i < length; i++) {
650                if (networks.get(i).networkId == info.getNetworkId()) {
651                    return networks.get(i).SSID;
652                }
653            }
654            return null;
655        }
656
657        @VisibleForTesting
658        void setActivity(int wifiActivity) {
659            mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
660                    || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
661            mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
662                    || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
663            notifyListenersIfNecessary();
664        }
665
666        /**
667         * Handler to receive the data activity on wifi.
668         */
669        class WifiHandler extends Handler {
670            @Override
671            public void handleMessage(Message msg) {
672                switch (msg.what) {
673                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
674                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
675                            mWifiChannel.sendMessage(Message.obtain(this,
676                                    AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
677                        } else {
678                            Log.e(mTag, "Failed to connect to wifi");
679                        }
680                        break;
681                    case WifiManager.DATA_ACTIVITY_NOTIFICATION:
682                        setActivity(msg.arg1);
683                        break;
684                    default:
685                        // Ignore
686                        break;
687                }
688            }
689        }
690
691        static class WifiState extends SignalController.State {
692            String ssid;
693
694            @Override
695            public void copyFrom(State s) {
696                WifiState state = (WifiState) s;
697                ssid = state.ssid;
698                super.copyFrom(s);
699            }
700
701            @Override
702            protected void toString(StringBuilder builder) {
703                builder.append("ssid=").append(ssid).append(',');
704                super.toString(builder);
705            }
706
707            @Override
708            public boolean equals(Object o) {
709                return super.equals(o)
710                        && Objects.equals(((WifiState) o).ssid, ssid);
711            }
712        }
713    }
714
715    static class MobileSignalController extends SignalController<MobileSignalController.MobileState,
716            MobileSignalController.MobileIconGroup> {
717        private final Config mConfig;
718        private final TelephonyManager mPhone;
719        private final String mNetworkNameDefault;
720        private final String mNetworkNameSeparator;
721
722        // @VisibleForDemoMode
723        Map<Integer, MobileIconGroup> mNetworkToIconLookup;
724
725        // Since some pieces of the phone state are interdependent we store it locally,
726        // this could potentially become part of MobileState for simplification/complication
727        // of code.
728        private IccCardConstants.State mSimState = IccCardConstants.State.READY;
729        private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
730        private int mDataState = TelephonyManager.DATA_DISCONNECTED;
731        private ServiceState mServiceState;
732        private SignalStrength mSignalStrength;
733        private MobileIconGroup mDefaultIcons;
734
735        // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
736        // need listener lists anymore.
737        public MobileSignalController(Context context, Config config, boolean hasMobileData,
738                TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
739                List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
740            super("MobileSignalController", context, ConnectivityManager.TYPE_MOBILE,
741                    signalCallbacks, signalClusters, networkController);
742            mConfig = config;
743            mPhone = phone;
744            mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
745            mNetworkNameDefault = getStringIfExists(
746                    com.android.internal.R.string.lockscreen_carrier_default);
747
748            mapIconSets();
749
750            mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
751            mLastState.enabled = mCurrentState.enabled = hasMobileData;
752            mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
753        }
754
755        /**
756         * Get (the mobile parts of) the carrier string.
757         *
758         * @param currentLabel can be used for concatenation, currently just empty
759         * @param connected whether the device has connection to the internet at all
760         * @param isMobileLabel whether to always return the network or just when data is connected
761         */
762        public String getLabel(String currentLabel, boolean connected, boolean isMobileLabel) {
763            if (!mCurrentState.enabled) {
764                return "";
765            } else {
766                String mobileLabel = "";
767                // We want to show the carrier name if in service and either:
768                // - We are connected to mobile data, or
769                // - We are not connected to mobile data, as long as the *reason* packets are not
770                //   being routed over that link is that we have better connectivity via wifi.
771                // If data is disconnected for some other reason but wifi (or ethernet/bluetooth)
772                // is connected, we show nothing.
773                // Otherwise (nothing connected) we show "No internet connection".
774                if (mCurrentState.dataConnected) {
775                    mobileLabel = mCurrentState.networkName;
776                } else if (connected || mCurrentState.isEmergency) {
777                    if (mCurrentState.connected || mCurrentState.isEmergency) {
778                        // The isEmergencyOnly test covers the case of a phone with no SIM
779                        mobileLabel = mCurrentState.networkName;
780                    }
781                } else {
782                    mobileLabel = mContext
783                            .getString(R.string.status_bar_settings_signal_meter_disconnected);
784                }
785
786                // Now for things that should only be shown when actually using mobile data.
787                if (isMobileLabel) {
788                    return mobileLabel;
789                } else {
790                    return mCurrentState.dataConnected ? mobileLabel : currentLabel;
791                }
792            }
793        }
794
795        public int getDataContentDescription() {
796            return getIcons().mDataContentDescription;
797        }
798
799        public void setAirplaneMode(boolean airplaneMode) {
800            mCurrentState.airplaneMode = airplaneMode;
801            notifyListenersIfNecessary();
802        }
803
804        public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
805            // For mobile data, use general inet condition for phone signal indexing,
806            // and network specific for data indexing (I think this might be a bug, but
807            // keeping for now).
808            // TODO: Update with explanation of why.
809            mCurrentState.inetForNetwork = inetConditionForNetwork;
810            setInetCondition(inetCondition);
811        }
812
813        /**
814         * Start listening for phone state changes.
815         */
816        public void registerListener() {
817            mPhone.listen(mPhoneStateListener,
818                    PhoneStateListener.LISTEN_SERVICE_STATE
819                            | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
820                            | PhoneStateListener.LISTEN_CALL_STATE
821                            | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
822                            | PhoneStateListener.LISTEN_DATA_ACTIVITY);
823        }
824
825        /**
826         * Stop listening for phone state changes.
827         */
828        public void unregisterListener() {
829            mPhone.listen(mPhoneStateListener, 0);
830        }
831
832        /**
833         * Produce a mapping of data network types to icon groups for simple and quick use in
834         * updateTelephony.
835         *
836         * TODO: See if config can change with locale, this may need to be regenerated on Locale
837         * change.
838         */
839        private void mapIconSets() {
840            mNetworkToIconLookup = new HashMap<Integer, MobileIconGroup>();
841
842            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
843            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
844            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
845            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
846            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
847
848            if (!mConfig.showAtLeastThreeGees) {
849                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
850                        TelephonyIcons.UNKNOWN);
851                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
852                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
853                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
854
855                mDefaultIcons = TelephonyIcons.G;
856            } else {
857                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
858                        TelephonyIcons.THREE_G);
859                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
860                        TelephonyIcons.THREE_G);
861                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
862                        TelephonyIcons.THREE_G);
863                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
864                        TelephonyIcons.THREE_G);
865                mDefaultIcons = TelephonyIcons.THREE_G;
866            }
867
868            MobileIconGroup hGroup = TelephonyIcons.THREE_G;
869            if (mConfig.hspaDataDistinguishable) {
870                hGroup = TelephonyIcons.H;
871            }
872            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
873            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
874            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
875            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
876
877            if (mConfig.show4gForLte) {
878                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
879            } else {
880                mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
881            }
882        }
883
884        /**
885         * {@inheritDoc}
886         */
887        @Override
888        public void notifyListeners() {
889            MobileIconGroup icons = getIcons();
890
891            String contentDescription = getStringIfExists(getContentDescription());
892            String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
893            int qsTypeIcon = icons.mQsDataType[mCurrentState.inetForNetwork];
894            int length = mSignalsChangedCallbacks.size();
895            for (int i = 0; i < length; i++) {
896                mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
897                        && !mCurrentState.isEmergency && !mCurrentState.airplaneMode,
898                        getQsCurrentIconId(), contentDescription,
899                        qsTypeIcon,
900                        mCurrentState.dataConnected && mCurrentState.activityIn,
901                        mCurrentState.dataConnected && mCurrentState.activityOut,
902                        dataContentDescription,
903                        mCurrentState.isEmergency ? null : mCurrentState.networkName,
904                        mCurrentState.noSim,
905                        // Only wide if actually showing something.
906                        icons.mIsWide && qsTypeIcon != 0);
907            }
908            boolean showDataIcon = mCurrentState.inetForNetwork != 0
909                    || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
910            int typeIcon = showDataIcon ? icons.mDataType : 0;
911            int signalClustersLength = mSignalClusters.size();
912            for (int i = 0; i < signalClustersLength; i++) {
913                mSignalClusters.get(i).setMobileDataIndicators(
914                        mCurrentState.enabled && !mCurrentState.airplaneMode,
915                        getCurrentIconId(),
916                        typeIcon,
917                        contentDescription,
918                        dataContentDescription,
919                        // Only wide if actually showing something.
920                        icons.mIsWide && typeIcon != 0);
921            }
922        }
923
924        @Override
925        public MobileState cleanState() {
926            return new MobileState();
927        }
928
929        private boolean hasService() {
930            if (mServiceState != null) {
931                // Consider the device to be in service if either voice or data
932                // service is available. Some SIM cards are marketed as data-only
933                // and do not support voice service, and on these SIM cards, we
934                // want to show signal bars for data service as well as the "no
935                // service" or "emergency calls only" text that indicates that voice
936                // is not available.
937                switch (mServiceState.getVoiceRegState()) {
938                    case ServiceState.STATE_POWER_OFF:
939                        return false;
940                    case ServiceState.STATE_OUT_OF_SERVICE:
941                    case ServiceState.STATE_EMERGENCY_ONLY:
942                        return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
943                    default:
944                        return true;
945                }
946            } else {
947                return false;
948            }
949        }
950
951        private boolean isCdma() {
952            return (mSignalStrength != null) && !mSignalStrength.isGsm();
953        }
954
955        public boolean isEmergencyOnly() {
956            return (mServiceState != null && mServiceState.isEmergencyOnly());
957        }
958
959        private boolean isRoaming() {
960            if (isCdma()) {
961                final int iconMode = mServiceState.getCdmaEriIconMode();
962                return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
963                        && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
964                            || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
965            } else {
966                return mServiceState != null && mServiceState.getRoaming();
967            }
968        }
969
970        public void handleBroadcast(Intent intent) {
971            String action = intent.getAction();
972            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
973                String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
974                final String lockedReason =
975                        intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
976                updateSimState(stateExtra, lockedReason);
977                updateTelephony();
978            } else if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
979                updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
980                        intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
981                        intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
982                        intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
983                notifyListenersIfNecessary();
984            }
985        }
986
987        /**
988         * Determines the current sim state, based on a TelephonyIntents.ACTION_SIM_STATE_CHANGED
989         * broadcast.
990         */
991        private final void updateSimState(String stateExtra, String lockedReason) {
992            if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
993                mSimState = IccCardConstants.State.ABSENT;
994            } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
995                mSimState = IccCardConstants.State.READY;
996            } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
997                if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
998                    mSimState = IccCardConstants.State.PIN_REQUIRED;
999                } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
1000                    mSimState = IccCardConstants.State.PUK_REQUIRED;
1001                } else {
1002                    mSimState = IccCardConstants.State.NETWORK_LOCKED;
1003                }
1004            } else {
1005                mSimState = IccCardConstants.State.UNKNOWN;
1006            }
1007            if (DEBUG) Log.d(TAG, "updateSimState: mSimState=" + mSimState);
1008        }
1009
1010        /**
1011         * Updates the network's name based on incoming spn and plmn.
1012         */
1013        void updateNetworkName(boolean showSpn, String spn, boolean showPlmn, String plmn) {
1014            if (CHATTY) {
1015                Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn + " spn=" + spn
1016                        + " showPlmn=" + showPlmn + " plmn=" + plmn);
1017            }
1018            StringBuilder str = new StringBuilder();
1019            if (showPlmn && plmn != null) {
1020                str.append(plmn);
1021            }
1022            if (showSpn && spn != null) {
1023                if (str.length() != 0) {
1024                    str.append(mNetworkNameSeparator);
1025                }
1026                str.append(spn);
1027            }
1028            if (str.length() != 0) {
1029                mCurrentState.networkName = str.toString();
1030            } else {
1031                mCurrentState.networkName = mNetworkNameDefault;
1032            }
1033        }
1034
1035        /**
1036         * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
1037         * mDataState, and mSimState.  It should be called any time one of these is updated.
1038         * This will call listeners if necessary.
1039         */
1040        private final void updateTelephony() {
1041            if (DEBUG) {
1042                Log.d(TAG, "updateTelephonySignalStrength: hasService=" + hasService()
1043                        + " ss=" + mSignalStrength);
1044            }
1045            mCurrentState.connected = hasService() && mSignalStrength != null;
1046            if (mCurrentState.connected) {
1047                if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
1048                    mCurrentState.level = mSignalStrength.getCdmaLevel();
1049                } else {
1050                    mCurrentState.level = mSignalStrength.getLevel();
1051                }
1052            }
1053            if (mNetworkToIconLookup.containsKey(mDataNetType)) {
1054                mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
1055            } else {
1056                mCurrentState.iconGroup = mDefaultIcons;
1057            }
1058            mCurrentState.dataConnected = mCurrentState.connected
1059                    && mDataState == TelephonyManager.DATA_CONNECTED;
1060            if (!isCdma()) {
1061                if (mSimState == IccCardConstants.State.READY ||
1062                        mSimState == IccCardConstants.State.UNKNOWN) {
1063                    mCurrentState.noSim = false;
1064                } else {
1065                    mCurrentState.noSim = true;
1066                    // No sim, no data.
1067                    mCurrentState.dataConnected = false;
1068                }
1069            }
1070
1071            if (isRoaming()) {
1072                mCurrentState.iconGroup = TelephonyIcons.ROAMING;
1073            }
1074            if (isEmergencyOnly() != mCurrentState.isEmergency) {
1075                mCurrentState.isEmergency = isEmergencyOnly();
1076                mNetworkController.recalculateEmergency();
1077            }
1078            notifyListenersIfNecessary();
1079        }
1080
1081        @VisibleForTesting
1082        void setActivity(int activity) {
1083            mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
1084                    || activity == TelephonyManager.DATA_ACTIVITY_IN;
1085            mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
1086                    || activity == TelephonyManager.DATA_ACTIVITY_OUT;
1087            notifyListenersIfNecessary();
1088        }
1089
1090        @Override
1091        public void dump(PrintWriter pw) {
1092            super.dump(pw);
1093            pw.println("  mServiceState=" + mServiceState + ",");
1094            pw.println("  mSignalStrength=" + mSignalStrength + ",");
1095            pw.println("  mDataState=" + mDataState + ",");
1096            pw.println("  mDataNetType=" + mDataNetType + ",");
1097        }
1098
1099        PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
1100            @Override
1101            public void onSignalStrengthsChanged(SignalStrength signalStrength) {
1102                if (DEBUG) {
1103                    Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength +
1104                            ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
1105                }
1106                mSignalStrength = signalStrength;
1107                updateTelephony();
1108            }
1109
1110            @Override
1111            public void onServiceStateChanged(ServiceState state) {
1112                if (DEBUG) {
1113                    Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
1114                            + " dataState=" + state.getDataRegState());
1115                }
1116                mServiceState = state;
1117                updateTelephony();
1118            }
1119
1120            @Override
1121            public void onDataConnectionStateChanged(int state, int networkType) {
1122                if (DEBUG) {
1123                    Log.d(TAG, "onDataConnectionStateChanged: state=" + state
1124                            + " type=" + networkType);
1125                }
1126                mDataState = state;
1127                mDataNetType = networkType;
1128                updateTelephony();
1129            }
1130
1131            @Override
1132            public void onDataActivity(int direction) {
1133                if (DEBUG) {
1134                    Log.d(TAG, "onDataActivity: direction=" + direction);
1135                }
1136                setActivity(direction);
1137            }
1138        };
1139
1140        static class MobileIconGroup extends SignalController.IconGroup {
1141            final int mDataContentDescription; // mContentDescriptionDataType
1142            final int mDataType;
1143            final boolean mIsWide;
1144            final int[] mQsDataType;
1145
1146            public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
1147                    int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
1148                    int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
1149                    int[] qsDataType) {
1150                super(name, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState, sbDiscState,
1151                        qsDiscState, discContentDesc);
1152                mDataContentDescription = dataContentDesc;
1153                mDataType = dataType;
1154                mIsWide = isWide;
1155                mQsDataType = qsDataType;
1156            }
1157        }
1158
1159        static class MobileState extends SignalController.State {
1160            String networkName;
1161            boolean noSim;
1162            boolean dataConnected;
1163            boolean isEmergency;
1164            boolean airplaneMode;
1165            int inetForNetwork;
1166
1167            @Override
1168            public void copyFrom(State s) {
1169                MobileState state = (MobileState) s;
1170                noSim = state.noSim;
1171                networkName = state.networkName;
1172                dataConnected = state.dataConnected;
1173                inetForNetwork = state.inetForNetwork;
1174                isEmergency = state.isEmergency;
1175                airplaneMode = state.airplaneMode;
1176                super.copyFrom(s);
1177            }
1178
1179            @Override
1180            protected void toString(StringBuilder builder) {
1181                builder.append("noSim=").append(noSim).append(',');
1182                builder.append("networkName=").append(networkName).append(',');
1183                builder.append("dataConnected=").append(dataConnected).append(',');
1184                builder.append("inetForNetwork=").append(inetForNetwork).append(',');
1185                builder.append("isEmergency=").append(isEmergency).append(',');
1186                builder.append("airplaneMode=").append(airplaneMode).append(',');
1187                super.toString(builder);
1188            }
1189
1190            @Override
1191            public boolean equals(Object o) {
1192                return super.equals(o)
1193                        && Objects.equals(((MobileState) o).networkName, networkName)
1194                        && ((MobileState) o).noSim == noSim
1195                        && ((MobileState) o).dataConnected == dataConnected
1196                        && ((MobileState) o).isEmergency == isEmergency
1197                        && ((MobileState) o).airplaneMode == airplaneMode
1198                        && ((MobileState) o).inetForNetwork == inetForNetwork;
1199            }
1200        }
1201    }
1202
1203    /**
1204     * Common base class for handling signal for both wifi and mobile data.
1205     */
1206    static abstract class SignalController<T extends SignalController.State,
1207            I extends SignalController.IconGroup> {
1208        protected final String mTag;
1209        protected final T mCurrentState;
1210        protected final T mLastState;
1211        protected final int mNetworkType;
1212        protected final Context mContext;
1213        // The owner of the SignalController (i.e. NetworkController will maintain the following
1214        // lists and call notifyListeners whenever the list has changed to ensure everyone
1215        // is aware of current state.
1216        protected final List<NetworkSignalChangedCallback> mSignalsChangedCallbacks;
1217        protected final List<SignalCluster> mSignalClusters;
1218        protected final NetworkControllerImpl mNetworkController;
1219
1220        // Save the previous HISTORY_SIZE states for logging.
1221        private final State[] mHistory;
1222        // Where to copy the next state into.
1223        private int mHistoryIndex;
1224
1225        public SignalController(String tag, Context context, int type,
1226                List<NetworkSignalChangedCallback> signalCallbacks,
1227                List<SignalCluster> signalClusters, NetworkControllerImpl networkController) {
1228            mTag = TAG + "::" + tag;
1229            mNetworkController = networkController;
1230            mNetworkType = type;
1231            mContext = context;
1232            mSignalsChangedCallbacks = signalCallbacks;
1233            mSignalClusters = signalClusters;
1234            mCurrentState = cleanState();
1235            mLastState = cleanState();
1236            if (RECORD_HISTORY) {
1237                mHistory = new State[HISTORY_SIZE];
1238                for (int i = 0; i < HISTORY_SIZE; i++) {
1239                    mHistory[i] = cleanState();
1240                }
1241            }
1242        }
1243
1244        public T getState() {
1245            return mCurrentState;
1246        }
1247
1248        public int getNetworkType() {
1249            return mNetworkType;
1250        }
1251
1252        public void setInetCondition(int inetCondition) {
1253            mCurrentState.inetCondition = inetCondition;
1254            notifyListenersIfNecessary();
1255        }
1256
1257        // @VisibleForDemoMode
1258        /**
1259         * Used at the end of demo mode to clear out any ugly state that it has created.
1260         * Since we haven't had any callbacks, then isDirty will not have been triggered,
1261         * so we can just take the last good state directly from there.
1262         */
1263        void resetLastState() {
1264            mCurrentState.copyFrom(mLastState);
1265        }
1266
1267        /**
1268         * Determines if the state of this signal controller has changed and
1269         * needs to trigger callbacks related to it.
1270         */
1271        public boolean isDirty() {
1272            if (!mLastState.equals(mCurrentState)) {
1273                if (DEBUG) {
1274                    Log.d(mTag, "Change in state from: " + mLastState + "\n"
1275                            + "\tto: " + mCurrentState);
1276                }
1277                return true;
1278            }
1279            return false;
1280        }
1281
1282        public void saveLastState() {
1283            if (RECORD_HISTORY) {
1284                recordLast();
1285            }
1286            // Updates the current time.
1287            mCurrentState.time = System.currentTimeMillis();
1288            mLastState.copyFrom(mCurrentState);
1289        }
1290
1291        /**
1292         * Gets the signal icon for QS based on current state of connected, enabled, and level.
1293         */
1294        public int getQsCurrentIconId() {
1295            if (mCurrentState.connected) {
1296                return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
1297            } else if (mCurrentState.enabled) {
1298                return getIcons().mQsDiscState;
1299            } else {
1300                return getIcons().mQsNullState;
1301            }
1302        }
1303
1304        /**
1305         * Gets the signal icon for SB based on current state of connected, enabled, and level.
1306         */
1307        public int getCurrentIconId() {
1308            if (mCurrentState.connected) {
1309                return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
1310            } else if (mCurrentState.enabled) {
1311                return getIcons().mSbDiscState;
1312            } else {
1313                return getIcons().mSbNullState;
1314            }
1315        }
1316
1317        /**
1318         * Gets the content description for the signal based on current state of connected and
1319         * level.
1320         */
1321        public int getContentDescription() {
1322            if (mCurrentState.connected) {
1323                return getIcons().mContentDesc[mCurrentState.level];
1324            } else {
1325                return getIcons().mDiscContentDesc;
1326            }
1327        }
1328
1329        protected void notifyListenersIfNecessary() {
1330            if (isDirty()) {
1331                saveLastState();
1332                notifyListeners();
1333                mNetworkController.refreshCarrierLabel();
1334            }
1335        }
1336
1337        /**
1338         * Returns the resource if resId is not 0, and an empty string otherwise.
1339         */
1340        protected String getStringIfExists(int resId) {
1341            return resId != 0 ? mContext.getString(resId) : "";
1342        }
1343
1344        protected I getIcons() {
1345            return (I) mCurrentState.iconGroup;
1346        }
1347
1348        /**
1349         * Saves the last state of any changes, so we can log the current
1350         * and last value of any state data.
1351         */
1352        protected void recordLast() {
1353            mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
1354        }
1355
1356        public void dump(PrintWriter pw) {
1357            pw.println("  - " + mTag + " -----");
1358            pw.println("  Current State: " + mCurrentState);
1359            if (RECORD_HISTORY) {
1360                // Count up the states that actually contain time stamps, and only display those.
1361                int size = 0;
1362                for (int i = 0; i < HISTORY_SIZE; i++) {
1363                    if (mHistory[i].time != 0) size++;
1364                }
1365                // Print out the previous states in ordered number.
1366                for (int i = mHistoryIndex + HISTORY_SIZE - 1;
1367                        i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
1368                    pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + ": "
1369                            + mHistory[i & (HISTORY_SIZE - 1)]);
1370                }
1371            }
1372        }
1373
1374        /**
1375         * Trigger callbacks based on current state.  The callbacks should be completely
1376         * based on current state, and only need to be called in the scenario where
1377         * mCurrentState != mLastState.
1378         */
1379        public abstract void notifyListeners();
1380
1381        /**
1382         * Generate a blank T.
1383         */
1384        public abstract T cleanState();
1385
1386        /*
1387         * Holds icons for a given state. Arrays are generally indexed as inet
1388         * state (full connectivity or not) first, and second dimension as
1389         * signal strength.
1390         */
1391        static class IconGroup {
1392            final int[][] mSbIcons;
1393            final int[][] mQsIcons;
1394            final int[] mContentDesc;
1395            final int mSbNullState;
1396            final int mQsNullState;
1397            final int mSbDiscState;
1398            final int mQsDiscState;
1399            final int mDiscContentDesc;
1400            // For logging.
1401            final String mName;
1402
1403            public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
1404                    int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
1405                    int discContentDesc) {
1406                mName = name;
1407                mSbIcons = sbIcons;
1408                mQsIcons = qsIcons;
1409                mContentDesc = contentDesc;
1410                mSbNullState = sbNullState;
1411                mQsNullState = qsNullState;
1412                mSbDiscState = sbDiscState;
1413                mQsDiscState = qsDiscState;
1414                mDiscContentDesc = discContentDesc;
1415            }
1416
1417            @Override
1418            public String toString() {
1419                return "IconGroup(" + mName + ")";
1420            }
1421        }
1422
1423        static class State {
1424            boolean connected;
1425            boolean enabled;
1426            boolean activityIn;
1427            boolean activityOut;
1428            int level;
1429            IconGroup iconGroup;
1430            int inetCondition;
1431            int rssi; // Only for logging.
1432
1433            // Not used for comparison, just used for logging.
1434            long time;
1435
1436            public void copyFrom(State state) {
1437                connected = state.connected;
1438                enabled = state.enabled;
1439                level = state.level;
1440                iconGroup = state.iconGroup;
1441                inetCondition = state.inetCondition;
1442                activityIn = state.activityIn;
1443                activityOut = state.activityOut;
1444                rssi = state.rssi;
1445                time = state.time;
1446            }
1447
1448            @Override
1449            public String toString() {
1450                if (time != 0) {
1451                    StringBuilder builder = new StringBuilder();
1452                    toString(builder);
1453                    return builder.toString();
1454                } else {
1455                    return "Empty " + getClass().getSimpleName();
1456                }
1457            }
1458
1459            protected void toString(StringBuilder builder) {
1460                builder.append("connected=").append(connected).append(',')
1461                        .append("enabled=").append(enabled).append(',')
1462                        .append("level=").append(level).append(',')
1463                        .append("inetCondition=").append(inetCondition).append(',')
1464                        .append("iconGroup=").append(iconGroup).append(',')
1465                        .append("activityIn=").append(activityIn).append(',')
1466                        .append("activityOut=").append(activityOut).append(',')
1467                        .append("rssi=").append(rssi).append(',')
1468                        .append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
1469            }
1470
1471            @Override
1472            public boolean equals(Object o) {
1473                if (!o.getClass().equals(getClass())) {
1474                    return false;
1475                }
1476                State other = (State) o;
1477                return other.connected == connected
1478                        && other.enabled == enabled
1479                        && other.level == level
1480                        && other.inetCondition == inetCondition
1481                        && other.iconGroup == iconGroup
1482                        && other.activityIn == activityIn
1483                        && other.activityOut == activityOut
1484                        && other.rssi == rssi;
1485            }
1486        }
1487    }
1488
1489    public interface SignalCluster {
1490        void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription);
1491
1492        void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
1493                String contentDescription, String typeContentDescription, boolean isTypeIconWide);
1494
1495        void setIsAirplaneMode(boolean is, int airplaneIcon, int contentDescription);
1496    }
1497
1498    public interface EmergencyListener {
1499        void setEmergencyCallsOnly(boolean emergencyOnly);
1500    }
1501
1502    public interface CarrierLabelListener {
1503        void setCarrierLabel(String label);
1504    }
1505
1506    @VisibleForTesting
1507    static class Config {
1508        boolean showAtLeastThreeGees = false;
1509        boolean alwaysShowCdmaRssi = false;
1510        boolean show4gForLte = false;
1511        boolean hspaDataDistinguishable;
1512
1513        static Config readConfig(Context context) {
1514            Config config = new Config();
1515            Resources res = context.getResources();
1516
1517            config.showAtLeastThreeGees = res.getBoolean(R.bool.config_showMin3G);
1518            config.alwaysShowCdmaRssi =
1519                    res.getBoolean(com.android.internal.R.bool.config_alwaysUseCdmaRssi);
1520            config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
1521            config.hspaDataDistinguishable =
1522                    res.getBoolean(R.bool.config_hspa_data_distinguishable);
1523            return config;
1524        }
1525    }
1526}
1527