MobileSignalController.java revision 5504d39b7f880fa8b8660e5d4b014ec559124cf9
1/*
2 * Copyright (C) 2015 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 */
16package com.android.systemui.statusbar.policy;
17
18import android.content.Context;
19import android.content.Intent;
20import android.net.NetworkCapabilities;
21import android.telephony.PhoneStateListener;
22import android.telephony.ServiceState;
23import android.telephony.SignalStrength;
24import android.telephony.SubscriptionInfo;
25import android.telephony.SubscriptionManager;
26import android.telephony.TelephonyManager;
27import android.util.Log;
28import android.util.SparseArray;
29
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.internal.telephony.TelephonyIntents;
32import com.android.internal.telephony.cdma.EriInfo;
33import com.android.systemui.R;
34import com.android.systemui.statusbar.policy.NetworkController.NetworkSignalChangedCallback;
35import com.android.systemui.statusbar.policy.NetworkControllerImpl.Config;
36import com.android.systemui.statusbar.policy.NetworkControllerImpl.SignalCluster;
37
38import java.io.PrintWriter;
39import java.util.List;
40import java.util.Objects;
41
42
43public class MobileSignalController extends SignalController<
44        MobileSignalController.MobileState, MobileSignalController.MobileIconGroup> {
45    private final TelephonyManager mPhone;
46    private final String mNetworkNameDefault;
47    private final String mNetworkNameSeparator;
48    @VisibleForTesting
49    final PhoneStateListener mPhoneStateListener;
50    // Save entire info for logging, we only use the id.
51    private final SubscriptionInfo mSubscriptionInfo;
52
53    // @VisibleForDemoMode
54    final SparseArray<MobileIconGroup> mNetworkToIconLookup;
55
56    // Since some pieces of the phone state are interdependent we store it locally,
57    // this could potentially become part of MobileState for simplification/complication
58    // of code.
59    private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
60    private int mDataState = TelephonyManager.DATA_DISCONNECTED;
61    private ServiceState mServiceState;
62    private SignalStrength mSignalStrength;
63    private MobileIconGroup mDefaultIcons;
64    private Config mConfig;
65
66    // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
67    // need listener lists anymore.
68    public MobileSignalController(Context context, Config config, boolean hasMobileData,
69            TelephonyManager phone, List<NetworkSignalChangedCallback> signalCallbacks,
70            List<SignalCluster> signalClusters, NetworkControllerImpl networkController,
71            SubscriptionInfo info) {
72        super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
73                NetworkCapabilities.TRANSPORT_CELLULAR, signalCallbacks, signalClusters,
74                networkController);
75        mNetworkToIconLookup = new SparseArray<>();
76        mConfig = config;
77        mPhone = phone;
78        mSubscriptionInfo = info;
79        mPhoneStateListener = new MobilePhoneStateListener(info.getSubscriptionId());
80        mNetworkNameSeparator = getStringIfExists(R.string.status_bar_network_name_separator);
81        mNetworkNameDefault = getStringIfExists(
82                com.android.internal.R.string.lockscreen_carrier_default);
83
84        mapIconSets();
85
86        mLastState.networkName = mCurrentState.networkName = mNetworkNameDefault;
87        mLastState.networkNameData = mCurrentState.networkNameData = mNetworkNameDefault;
88        mLastState.enabled = mCurrentState.enabled = hasMobileData;
89        mLastState.iconGroup = mCurrentState.iconGroup = mDefaultIcons;
90        // Get initial data sim state.
91        updateDataSim();
92    }
93
94    public void setConfiguration(Config config) {
95        mConfig = config;
96        mapIconSets();
97        updateTelephony();
98    }
99
100    public int getDataContentDescription() {
101        return getIcons().mDataContentDescription;
102    }
103
104    public void setAirplaneMode(boolean airplaneMode) {
105        mCurrentState.airplaneMode = airplaneMode;
106        notifyListenersIfNecessary();
107    }
108
109    public void setInetCondition(int inetCondition, int inetConditionForNetwork) {
110        // For mobile data, use general inet condition for phone signal indexing,
111        // and network specific for data indexing (I think this might be a bug, but
112        // keeping for now).
113        // TODO: Update with explanation of why.
114        mCurrentState.inetForNetwork = inetConditionForNetwork;
115        setInetCondition(inetCondition);
116    }
117
118    public void setCarrierNetworkChangeMode(boolean carrierNetworkChangeMode) {
119        mCurrentState.carrierNetworkChangeMode = carrierNetworkChangeMode;
120        notifyListenersIfNecessary();
121    }
122
123    /**
124     * Start listening for phone state changes.
125     */
126    public void registerListener() {
127        mPhone.listen(mPhoneStateListener,
128                PhoneStateListener.LISTEN_SERVICE_STATE
129                        | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
130                        | PhoneStateListener.LISTEN_CALL_STATE
131                        | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
132                        | PhoneStateListener.LISTEN_DATA_ACTIVITY
133                        | PhoneStateListener.LISTEN_CARRIER_NETWORK_CHANGE);
134    }
135
136    /**
137     * Stop listening for phone state changes.
138     */
139    public void unregisterListener() {
140        mPhone.listen(mPhoneStateListener, 0);
141    }
142
143    /**
144     * Produce a mapping of data network types to icon groups for simple and quick use in
145     * updateTelephony.
146     */
147    private void mapIconSets() {
148        mNetworkToIconLookup.clear();
149
150        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
151        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
152        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
153        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
154        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
155
156        if (!mConfig.showAtLeast3G) {
157            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
158                    TelephonyIcons.UNKNOWN);
159            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
160            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
161            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
162
163            mDefaultIcons = TelephonyIcons.G;
164        } else {
165            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
166                    TelephonyIcons.THREE_G);
167            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
168                    TelephonyIcons.THREE_G);
169            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
170                    TelephonyIcons.THREE_G);
171            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
172                    TelephonyIcons.THREE_G);
173            mDefaultIcons = TelephonyIcons.THREE_G;
174        }
175
176        MobileIconGroup hGroup = TelephonyIcons.THREE_G;
177        if (mConfig.hspaDataDistinguishable) {
178            hGroup = TelephonyIcons.H;
179        }
180        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
181        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
182        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
183        mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
184
185        if (mConfig.show4gForLte) {
186            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
187        } else {
188            mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
189        }
190    }
191
192    @Override
193    public void notifyListeners() {
194        MobileIconGroup icons = getIcons();
195
196        String contentDescription = getStringIfExists(getContentDescription());
197        String dataContentDescription = getStringIfExists(icons.mDataContentDescription);
198
199        boolean showDataIcon = mCurrentState.dataConnected && mCurrentState.inetForNetwork != 0
200                || mCurrentState.iconGroup == TelephonyIcons.ROAMING;
201
202        // Only send data sim callbacks to QS.
203        if (mCurrentState.dataSim) {
204            int qsTypeIcon = showDataIcon ? icons.mQsDataType[mCurrentState.inetForNetwork] : 0;
205            int length = mSignalsChangedCallbacks.size();
206            for (int i = 0; i < length; i++) {
207                mSignalsChangedCallbacks.get(i).onMobileDataSignalChanged(mCurrentState.enabled
208                        && !mCurrentState.isEmergency,
209                        getQsCurrentIconId(), contentDescription,
210                        qsTypeIcon,
211                        mCurrentState.dataConnected
212                            && !mCurrentState.carrierNetworkChangeMode
213                            && mCurrentState.activityIn,
214                        mCurrentState.dataConnected
215                            && !mCurrentState.carrierNetworkChangeMode
216                            && mCurrentState.activityOut,
217                        dataContentDescription,
218                        mCurrentState.isEmergency ? null : mCurrentState.networkName,
219                        // Only wide if actually showing something.
220                        icons.mIsWide && qsTypeIcon != 0);
221            }
222        }
223        int typeIcon = showDataIcon ? icons.mDataType : 0;
224        int signalClustersLength = mSignalClusters.size();
225        for (int i = 0; i < signalClustersLength; i++) {
226            mSignalClusters.get(i).setMobileDataIndicators(
227                    mCurrentState.enabled && !mCurrentState.airplaneMode,
228                    getCurrentIconId(),
229                    getCurrentDarkIconId(),
230                    typeIcon,
231                    contentDescription,
232                    dataContentDescription,
233                    // Only wide if actually showing something.
234                    icons.mIsWide && typeIcon != 0,
235                    mSubscriptionInfo.getSubscriptionId());
236        }
237    }
238
239    private int getCurrentDarkIconId() {
240        return getCurrentIconId(false /* light */);
241    }
242
243    @Override
244    protected MobileState cleanState() {
245        return new MobileState();
246    }
247
248    private boolean hasService() {
249        if (mServiceState != null) {
250            // Consider the device to be in service if either voice or data
251            // service is available. Some SIM cards are marketed as data-only
252            // and do not support voice service, and on these SIM cards, we
253            // want to show signal bars for data service as well as the "no
254            // service" or "emergency calls only" text that indicates that voice
255            // is not available.
256            switch (mServiceState.getVoiceRegState()) {
257                case ServiceState.STATE_POWER_OFF:
258                    return false;
259                case ServiceState.STATE_OUT_OF_SERVICE:
260                case ServiceState.STATE_EMERGENCY_ONLY:
261                    return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
262                default:
263                    return true;
264            }
265        } else {
266            return false;
267        }
268    }
269
270    private boolean isCdma() {
271        return (mSignalStrength != null) && !mSignalStrength.isGsm();
272    }
273
274    public boolean isEmergencyOnly() {
275        return (mServiceState != null && mServiceState.isEmergencyOnly());
276    }
277
278    private boolean isRoaming() {
279        if (isCdma()) {
280            final int iconMode = mServiceState.getCdmaEriIconMode();
281            return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
282                    && (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
283                        || iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
284        } else {
285            return mServiceState != null && mServiceState.getRoaming();
286        }
287    }
288
289    private boolean isCarrierNetworkChangeActive() {
290        return !hasService() && mCurrentState.carrierNetworkChangeMode;
291    }
292
293    public void handleBroadcast(Intent intent) {
294        String action = intent.getAction();
295        if (action.equals(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION)) {
296            updateNetworkName(intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_SPN, false),
297                    intent.getStringExtra(TelephonyIntents.EXTRA_SPN),
298                    intent.getStringExtra(TelephonyIntents.EXTRA_DATA_SPN),
299                    intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
300                    intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
301            notifyListenersIfNecessary();
302        } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
303            updateDataSim();
304        }
305    }
306
307    private void updateDataSim() {
308        int defaultDataSub = SubscriptionManager.getDefaultDataSubId();
309        if (SubscriptionManager.isValidSubscriptionId(defaultDataSub)) {
310            mCurrentState.dataSim = defaultDataSub == mSubscriptionInfo.getSubscriptionId();
311        } else {
312            // There doesn't seem to be a data sim selected, however if
313            // there isn't a MobileSignalController with dataSim set, then
314            // QS won't get any callbacks and will be blank.  Instead
315            // lets just assume we are the data sim (which will basically
316            // show one at random) in QS until one is selected.  The user
317            // should pick one soon after, so we shouldn't be in this state
318            // for long.
319            mCurrentState.dataSim = true;
320        }
321        notifyListenersIfNecessary();
322    }
323
324    /**
325     * Updates the network's name based on incoming spn and plmn.
326     */
327    void updateNetworkName(boolean showSpn, String spn, String dataSpn,
328            boolean showPlmn, String plmn) {
329        if (CHATTY) {
330            Log.d("CarrierLabel", "updateNetworkName showSpn=" + showSpn
331                    + " spn=" + spn + " dataSpn=" + dataSpn
332                    + " showPlmn=" + showPlmn + " plmn=" + plmn);
333        }
334        StringBuilder str = new StringBuilder();
335        StringBuilder strData = new StringBuilder();
336        if (showPlmn && plmn != null) {
337            str.append(plmn);
338            strData.append(plmn);
339        }
340        if (showSpn && spn != null) {
341            if (str.length() != 0) {
342                str.append(mNetworkNameSeparator);
343            }
344            str.append(spn);
345        }
346        if (str.length() != 0) {
347            mCurrentState.networkName = str.toString();
348        } else {
349            mCurrentState.networkName = mNetworkNameDefault;
350        }
351        if (showSpn && dataSpn != null) {
352            if (strData.length() != 0) {
353                strData.append(mNetworkNameSeparator);
354            }
355            strData.append(dataSpn);
356        }
357        if (strData.length() != 0) {
358            mCurrentState.networkNameData = strData.toString();
359        } else {
360            mCurrentState.networkNameData = mNetworkNameDefault;
361        }
362    }
363
364    /**
365     * Updates the current state based on mServiceState, mSignalStrength, mDataNetType,
366     * mDataState, and mSimState.  It should be called any time one of these is updated.
367     * This will call listeners if necessary.
368     */
369    private final void updateTelephony() {
370        if (DEBUG) {
371            Log.d(mTag, "updateTelephonySignalStrength: hasService=" + hasService()
372                    + " ss=" + mSignalStrength);
373        }
374        mCurrentState.connected = hasService() && mSignalStrength != null;
375        if (mCurrentState.connected) {
376            if (!mSignalStrength.isGsm() && mConfig.alwaysShowCdmaRssi) {
377                mCurrentState.level = mSignalStrength.getCdmaLevel();
378            } else {
379                mCurrentState.level = mSignalStrength.getLevel();
380            }
381        }
382        if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
383            mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
384        } else {
385            mCurrentState.iconGroup = mDefaultIcons;
386        }
387        mCurrentState.dataConnected = mCurrentState.connected
388                && mDataState == TelephonyManager.DATA_CONNECTED;
389
390        if (isCarrierNetworkChangeActive()) {
391            mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
392        } else if (isRoaming()) {
393            mCurrentState.iconGroup = TelephonyIcons.ROAMING;
394        }
395        if (isEmergencyOnly() != mCurrentState.isEmergency) {
396            mCurrentState.isEmergency = isEmergencyOnly();
397            mNetworkController.recalculateEmergency();
398        }
399        // Fill in the network name if we think we have it.
400        if (mCurrentState.networkName == mNetworkNameDefault && mServiceState != null
401                && mServiceState.getOperatorAlphaShort() != null) {
402            mCurrentState.networkName = mServiceState.getOperatorAlphaShort();
403        }
404
405        notifyListenersIfNecessary();
406    }
407
408    @VisibleForTesting
409    void setActivity(int activity) {
410        mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
411                || activity == TelephonyManager.DATA_ACTIVITY_IN;
412        mCurrentState.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
413                || activity == TelephonyManager.DATA_ACTIVITY_OUT;
414        notifyListenersIfNecessary();
415    }
416
417    @Override
418    public void dump(PrintWriter pw) {
419        super.dump(pw);
420        pw.println("  mSubscription=" + mSubscriptionInfo + ",");
421        pw.println("  mServiceState=" + mServiceState + ",");
422        pw.println("  mSignalStrength=" + mSignalStrength + ",");
423        pw.println("  mDataState=" + mDataState + ",");
424        pw.println("  mDataNetType=" + mDataNetType + ",");
425    }
426
427    class MobilePhoneStateListener extends PhoneStateListener {
428        public MobilePhoneStateListener(int subId) {
429            super(subId);
430        }
431
432        @Override
433        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
434            if (DEBUG) {
435                Log.d(mTag, "onSignalStrengthsChanged signalStrength=" + signalStrength +
436                        ((signalStrength == null) ? "" : (" level=" + signalStrength.getLevel())));
437            }
438            mSignalStrength = signalStrength;
439            updateTelephony();
440        }
441
442        @Override
443        public void onServiceStateChanged(ServiceState state) {
444            if (DEBUG) {
445                Log.d(mTag, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
446                        + " dataState=" + state.getDataRegState());
447            }
448            mServiceState = state;
449            updateTelephony();
450        }
451
452        @Override
453        public void onDataConnectionStateChanged(int state, int networkType) {
454            if (DEBUG) {
455                Log.d(mTag, "onDataConnectionStateChanged: state=" + state
456                        + " type=" + networkType);
457            }
458            mDataState = state;
459            mDataNetType = networkType;
460            updateTelephony();
461        }
462
463        @Override
464        public void onDataActivity(int direction) {
465            if (DEBUG) {
466                Log.d(mTag, "onDataActivity: direction=" + direction);
467            }
468            setActivity(direction);
469        }
470
471        @Override
472        public void onCarrierNetworkChange(boolean active) {
473            if (DEBUG) {
474                Log.d(mTag, "onCarrierNetworkChange: active=" + active);
475            }
476            mCurrentState.carrierNetworkChangeMode = active;
477
478            updateTelephony();
479        }
480    };
481
482    static class MobileIconGroup extends SignalController.IconGroup {
483        final int mDataContentDescription; // mContentDescriptionDataType
484        final int mDataType;
485        final boolean mIsWide;
486        final int[] mQsDataType;
487
488        public MobileIconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
489                int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
490                int discContentDesc, int dataContentDesc, int dataType, boolean isWide,
491                int[] qsDataType) {
492            this(name, sbIcons, sbIcons, qsIcons, contentDesc, sbNullState, qsNullState,
493                    sbDiscState, sbDiscState, qsDiscState, discContentDesc, dataContentDesc,
494                    dataType, isWide, qsDataType);
495        }
496
497        public MobileIconGroup(String name, int[][] sbIcons, int[][] sbDarkIcons, int[][] qsIcons,
498                int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState,
499                int sbDarkDiscState, int qsDiscState, int discContentDesc, int dataContentDesc,
500                int dataType, boolean isWide, int[] qsDataType) {
501            super(name, sbIcons, sbDarkIcons, qsIcons, contentDesc, sbNullState, qsNullState,
502                    sbDiscState, sbDarkDiscState, qsDiscState, discContentDesc);
503            mDataContentDescription = dataContentDesc;
504            mDataType = dataType;
505            mIsWide = isWide;
506            mQsDataType = qsDataType;
507        }
508    }
509
510    static class MobileState extends SignalController.State {
511        String networkName;
512        String networkNameData;
513        boolean dataSim;
514        boolean dataConnected;
515        boolean isEmergency;
516        boolean airplaneMode;
517        boolean carrierNetworkChangeMode;
518        int inetForNetwork;
519
520        @Override
521        public void copyFrom(State s) {
522            super.copyFrom(s);
523            MobileState state = (MobileState) s;
524            dataSim = state.dataSim;
525            networkName = state.networkName;
526            networkNameData = state.networkNameData;
527            dataConnected = state.dataConnected;
528            inetForNetwork = state.inetForNetwork;
529            isEmergency = state.isEmergency;
530            airplaneMode = state.airplaneMode;
531            carrierNetworkChangeMode = state.carrierNetworkChangeMode;
532        }
533
534        @Override
535        protected void toString(StringBuilder builder) {
536            super.toString(builder);
537            builder.append(',');
538            builder.append("dataSim=").append(dataSim).append(',');
539            builder.append("networkName=").append(networkName).append(',');
540            builder.append("networkNameData=").append(networkNameData).append(',');
541            builder.append("dataConnected=").append(dataConnected).append(',');
542            builder.append("inetForNetwork=").append(inetForNetwork).append(',');
543            builder.append("isEmergency=").append(isEmergency).append(',');
544            builder.append("airplaneMode=").append(airplaneMode).append(',');
545            builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode);
546        }
547
548        @Override
549        public boolean equals(Object o) {
550            return super.equals(o)
551                    && Objects.equals(((MobileState) o).networkName, networkName)
552                    && Objects.equals(((MobileState) o).networkNameData, networkNameData)
553                    && ((MobileState) o).dataSim == dataSim
554                    && ((MobileState) o).dataConnected == dataConnected
555                    && ((MobileState) o).isEmergency == isEmergency
556                    && ((MobileState) o).airplaneMode == airplaneMode
557                    && ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
558                    && ((MobileState) o).inetForNetwork == inetForNetwork;
559        }
560    }
561}
562