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