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