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