1 /*
2  * Copyright (C) 2020 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.settingslib.mobile;
17 
18 import android.os.Handler;
19 import android.os.Looper;
20 import android.telephony.ServiceState;
21 import android.telephony.SignalStrength;
22 import android.telephony.SubscriptionInfo;
23 import android.telephony.SubscriptionManager;
24 import android.telephony.TelephonyCallback;
25 import android.telephony.TelephonyDisplayInfo;
26 import android.telephony.TelephonyManager;
27 import android.util.Log;
28 
29 /**
30  * Tracks the mobile signal status for the SysUI and Settings.
31  *
32  * This class is not threadsafe. All the mobile statuses monitored by this class is stored in
33  * MobileStatus. Whoever uses this class should only rely on the MobileStatusTracker#Callback
34  * to get the latest mobile statuses. Do not get mobile statues directly from
35  * MobileStatusTracker#MobileStatus.
36  */
37 public class MobileStatusTracker {
38     private static final String TAG = "MobileStatusTracker";
39     private final TelephonyManager mPhone;
40     private final SubscriptionInfo mSubscriptionInfo;
41     private final Callback mCallback;
42     private final MobileStatus mMobileStatus;
43     private final SubscriptionDefaults mDefaults;
44     private final Handler mReceiverHandler;
45     private final MobileTelephonyCallback mTelephonyCallback;
46 
47     private boolean mListening = false;
48 
49     /**
50      * MobileStatusTracker constructors
51      *
52      * @param phone The TelephonyManager which corresponds to the subscription being monitored.
53      * @param receiverLooper The Looper on which the callback will be invoked.
54      * @param info The subscription being monitored.
55      * @param defaults The wrapper of the SubscriptionManager.
56      * @param callback The callback to notify any changes of the mobile status, users should only
57      *                 use this callback to get the latest mobile status.
58      */
MobileStatusTracker(TelephonyManager phone, Looper receiverLooper, SubscriptionInfo info, SubscriptionDefaults defaults, Callback callback)59     public MobileStatusTracker(TelephonyManager phone, Looper receiverLooper,
60             SubscriptionInfo info, SubscriptionDefaults defaults, Callback callback) {
61         mPhone = phone;
62         mReceiverHandler = new Handler(receiverLooper);
63         mTelephonyCallback = new MobileTelephonyCallback();
64         mSubscriptionInfo = info;
65         mDefaults = defaults;
66         mCallback = callback;
67         mMobileStatus = new MobileStatus();
68         updateDataSim();
69         mReceiverHandler.post(() -> mCallback.onMobileStatusChanged(
70                 /* updateTelephony= */false, new MobileStatus(mMobileStatus)));
71     }
72 
getTelephonyCallback()73     public MobileTelephonyCallback getTelephonyCallback() {
74         return mTelephonyCallback;
75     }
76 
77     /**
78      * Config the MobileStatusTracker to start or stop monitoring platform signals.
79      */
setListening(boolean listening)80     public void setListening(boolean listening) {
81         if (mListening == listening) {
82             return;
83         }
84         mListening = listening;
85         if (listening) {
86             mPhone.registerTelephonyCallback(mReceiverHandler::post, mTelephonyCallback);
87         } else {
88             mPhone.unregisterTelephonyCallback(mTelephonyCallback);
89         }
90     }
91 
isListening()92     public boolean isListening() {
93         return mListening;
94     }
95 
updateDataSim()96     private void updateDataSim() {
97         int activeDataSubId = mDefaults.getActiveDataSubId();
98         if (SubscriptionManager.isValidSubscriptionId(activeDataSubId)) {
99             mMobileStatus.dataSim = activeDataSubId == mSubscriptionInfo.getSubscriptionId();
100         } else {
101             // There doesn't seem to be a data sim selected, however if
102             // there isn't a MobileSignalController with dataSim set, then
103             // QS won't get any callbacks and will be blank.  Instead
104             // lets just assume we are the data sim (which will basically
105             // show one at random) in QS until one is selected.  The user
106             // should pick one soon after, so we shouldn't be in this state
107             // for long.
108             mMobileStatus.dataSim = true;
109         }
110     }
111 
setActivity(int activity)112     private void setActivity(int activity) {
113         mMobileStatus.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
114                 || activity == TelephonyManager.DATA_ACTIVITY_IN;
115         mMobileStatus.activityOut = activity == TelephonyManager.DATA_ACTIVITY_INOUT
116                 || activity == TelephonyManager.DATA_ACTIVITY_OUT;
117     }
118 
119     public class MobileTelephonyCallback extends TelephonyCallback implements
120             TelephonyCallback.ServiceStateListener,
121             TelephonyCallback.SignalStrengthsListener,
122             TelephonyCallback.DataConnectionStateListener,
123             TelephonyCallback.DataActivityListener,
124             TelephonyCallback.CarrierNetworkListener,
125             TelephonyCallback.ActiveDataSubscriptionIdListener,
126             TelephonyCallback.DisplayInfoListener{
127 
128         @Override
onSignalStrengthsChanged(SignalStrength signalStrength)129         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
130             if (Log.isLoggable(TAG, Log.DEBUG)) {
131                 Log.d(TAG, "onSignalStrengthsChanged signalStrength=" + signalStrength
132                         + ((signalStrength == null) ? ""
133                                 : (" level=" + signalStrength.getLevel())));
134             }
135             mMobileStatus.signalStrength = signalStrength;
136             mCallback.onMobileStatusChanged(
137                     /* updateTelephony= */true, new MobileStatus(mMobileStatus));
138         }
139 
140         @Override
onServiceStateChanged(ServiceState state)141         public void onServiceStateChanged(ServiceState state) {
142             if (Log.isLoggable(TAG, Log.DEBUG)) {
143                 Log.d(TAG, "onServiceStateChanged voiceState="
144                         + (state == null ? "" : state.getState())
145                         + " dataState=" + (state == null ? "" : state.getDataRegistrationState()));
146             }
147             mMobileStatus.serviceState = state;
148             mCallback.onMobileStatusChanged(
149                     /* updateTelephony= */true, new MobileStatus(mMobileStatus));
150         }
151 
152         @Override
onDataConnectionStateChanged(int state, int networkType)153         public void onDataConnectionStateChanged(int state, int networkType) {
154             if (Log.isLoggable(TAG, Log.DEBUG)) {
155                 Log.d(TAG, "onDataConnectionStateChanged: state=" + state
156                         + " type=" + networkType);
157             }
158             mMobileStatus.dataState = state;
159             mCallback.onMobileStatusChanged(
160                     /* updateTelephony= */true, new MobileStatus(mMobileStatus));
161         }
162 
163         @Override
onDataActivity(int direction)164         public void onDataActivity(int direction) {
165             if (Log.isLoggable(TAG, Log.DEBUG)) {
166                 Log.d(TAG, "onDataActivity: direction=" + direction);
167             }
168             setActivity(direction);
169             mCallback.onMobileStatusChanged(
170                     /* updateTelephony= */false, new MobileStatus(mMobileStatus));
171         }
172 
173         @Override
onCarrierNetworkChange(boolean active)174         public void onCarrierNetworkChange(boolean active) {
175             if (Log.isLoggable(TAG, Log.DEBUG)) {
176                 Log.d(TAG, "onCarrierNetworkChange: active=" + active);
177             }
178             mMobileStatus.carrierNetworkChangeMode = active;
179             mCallback.onMobileStatusChanged(
180                     /* updateTelephony= */true, new MobileStatus(mMobileStatus));
181         }
182 
183         @Override
onActiveDataSubscriptionIdChanged(int subId)184         public void onActiveDataSubscriptionIdChanged(int subId) {
185             if (Log.isLoggable(TAG, Log.DEBUG)) {
186                 Log.d(TAG, "onActiveDataSubscriptionIdChanged: subId=" + subId);
187             }
188             updateDataSim();
189             mCallback.onMobileStatusChanged(
190                     /* updateTelephony= */true, new MobileStatus(mMobileStatus));
191         }
192 
193         @Override
onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo)194         public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
195             if (Log.isLoggable(TAG, Log.DEBUG)) {
196                 Log.d(TAG, "onDisplayInfoChanged: telephonyDisplayInfo=" + telephonyDisplayInfo);
197             }
198             mMobileStatus.telephonyDisplayInfo = telephonyDisplayInfo;
199             mCallback.onMobileStatusChanged(
200                     /* updateTelephony= */ true, new MobileStatus(mMobileStatus));
201         }
202     }
203 
204     /**
205      * Wrapper class of the SubscriptionManager, for mock testing purpose
206      */
207     public static class SubscriptionDefaults {
getDefaultVoiceSubId()208         public int getDefaultVoiceSubId() {
209             return SubscriptionManager.getDefaultVoiceSubscriptionId();
210         }
211 
getDefaultDataSubId()212         public int getDefaultDataSubId() {
213             return SubscriptionManager.getDefaultDataSubscriptionId();
214         }
215 
getActiveDataSubId()216         public int getActiveDataSubId() {
217             return SubscriptionManager.getActiveDataSubscriptionId();
218         }
219     }
220 
221     /**
222      * Wrapper class which contains all the mobile status tracked by MobileStatusTracker.
223      */
224     public static class MobileStatus {
225         public boolean activityIn;
226         public boolean activityOut;
227         public boolean dataSim;
228         public boolean carrierNetworkChangeMode;
229         public int dataState = TelephonyManager.DATA_DISCONNECTED;
230         public ServiceState serviceState;
231         public SignalStrength signalStrength;
232         public TelephonyDisplayInfo telephonyDisplayInfo =
233                 new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
234                         TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
235 
236         /**
237          * Empty constructor
238          */
MobileStatus()239         public MobileStatus() { }
240 
241         /**
242          * Copy constructors
243          *
244          * @param status Source MobileStatus
245          */
MobileStatus(MobileStatus status)246         public MobileStatus(MobileStatus status) {
247             copyFrom(status);
248         }
249 
copyFrom(MobileStatus status)250         protected void copyFrom(MobileStatus status) {
251             activityIn = status.activityIn;
252             activityOut = status.activityOut;
253             dataSim = status.dataSim;
254             carrierNetworkChangeMode = status.carrierNetworkChangeMode;
255             dataState = status.dataState;
256             // We don't do deep copy for the below members since they may be Mockito instances.
257             serviceState = status.serviceState;
258             signalStrength = status.signalStrength;
259             telephonyDisplayInfo = status.telephonyDisplayInfo;
260         }
261 
262         @Override
toString()263         public String toString() {
264             StringBuilder builder = new StringBuilder();
265             return builder.append("[activityIn=").append(activityIn).append(',')
266                 .append("activityOut=").append(activityOut).append(',')
267                 .append("dataSim=").append(dataSim).append(',')
268                 .append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode).append(',')
269                 .append("dataState=").append(dataState).append(',')
270                 .append("serviceState=").append(serviceState == null ? ""
271                         : "mVoiceRegState=" + serviceState.getState() + "("
272                                 + ServiceState.rilServiceStateToString(serviceState.getState())
273                                 + ")" + ", mDataRegState=" + serviceState.getDataRegState() + "("
274                                 + ServiceState.rilServiceStateToString(
275                                         serviceState.getDataRegState()) + ")")
276                                         .append(',')
277                 .append("signalStrength=").append(signalStrength == null ? ""
278                         : signalStrength.getLevel()).append(',')
279                 .append("telephonyDisplayInfo=").append(telephonyDisplayInfo == null ? ""
280                         : telephonyDisplayInfo.toString()).append(']').toString();
281         }
282     }
283 
284     /**
285      * Callback for notifying any changes of the mobile status.
286      *
287      * This callback will always be invoked on the receiverLooper which must be specified when
288      * MobileStatusTracker is constructed.
289      */
290     public interface Callback {
291         /**
292          * Notify the mobile status has been updated.
293          *
294          * @param updateTelephony Whether needs to update other Telephony related parameters, this
295          *                        is only used by SysUI.
296          * @param mobileStatus Holds the latest mobile statuses
297          */
onMobileStatusChanged(boolean updateTelephony, MobileStatus mobileStatus)298         void onMobileStatusChanged(boolean updateTelephony, MobileStatus mobileStatus);
299     }
300 }
301