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