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