1 /* 2 * Copyright (C) 2018 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 17 package com.android.internal.telephony; 18 19 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; 20 import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.os.AsyncResult; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.os.PowerManager; 28 import android.os.RegistrantList; 29 import android.os.storage.StorageManager; 30 import android.sysprop.TelephonyProperties; 31 import android.telephony.PhoneCapability; 32 import android.telephony.SubscriptionManager; 33 import android.telephony.TelephonyManager; 34 import android.util.Log; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.telephony.Rlog; 38 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.NoSuchElementException; 42 43 /** 44 * This class manages phone's configuration which defines the potential capability (static) of the 45 * phone and its current activated capability (current). 46 * It gets and monitors static and current phone capability from the modem; send broadcast 47 * if they change, and and sends commands to modem to enable or disable phones. 48 */ 49 public class PhoneConfigurationManager { 50 public static final String DSDA = "dsda"; 51 public static final String DSDS = "dsds"; 52 public static final String TSTS = "tsts"; 53 public static final String SSSS = ""; 54 private static final String LOG_TAG = "PhoneCfgMgr"; 55 private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100; 56 private static final int EVENT_GET_MODEM_STATUS = 101; 57 private static final int EVENT_GET_MODEM_STATUS_DONE = 102; 58 private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103; 59 60 private static PhoneConfigurationManager sInstance = null; 61 private final Context mContext; 62 private PhoneCapability mStaticCapability; 63 private final RadioConfig mRadioConfig; 64 private final Handler mHandler; 65 private final Phone[] mPhones; 66 private final Map<Integer, Boolean> mPhoneStatusMap; 67 private MockableInterface mMi = new MockableInterface(); 68 private TelephonyManager mTelephonyManager; 69 private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList(); 70 71 /** 72 * Init method to instantiate the object 73 * Should only be called once. 74 */ init(Context context)75 public static PhoneConfigurationManager init(Context context) { 76 synchronized (PhoneConfigurationManager.class) { 77 if (sInstance == null) { 78 sInstance = new PhoneConfigurationManager(context); 79 } else { 80 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 81 } 82 return sInstance; 83 } 84 } 85 86 /** 87 * Constructor. 88 * @param context context needed to send broadcast. 89 */ PhoneConfigurationManager(Context context)90 private PhoneConfigurationManager(Context context) { 91 mContext = context; 92 // TODO: send commands to modem once interface is ready. 93 mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); 94 //initialize with default, it'll get updated when RADIO is ON/AVAILABLE 95 mStaticCapability = getDefaultCapability(); 96 mRadioConfig = RadioConfig.getInstance(mContext); 97 mHandler = new ConfigManagerHandler(); 98 mPhoneStatusMap = new HashMap<>(); 99 100 notifyCapabilityChanged(); 101 102 mPhones = PhoneFactory.getPhones(); 103 104 if (!StorageManager.inCryptKeeperBounce()) { 105 for (Phone phone : mPhones) { 106 phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone); 107 } 108 } else { 109 for (Phone phone : mPhones) { 110 phone.mCi.registerForOn(mHandler, Phone.EVENT_RADIO_ON, phone); 111 } 112 } 113 } 114 getDefaultCapability()115 private PhoneCapability getDefaultCapability() { 116 if (getPhoneCount() > 1) { 117 return PhoneCapability.DEFAULT_DSDS_CAPABILITY; 118 } else { 119 return PhoneCapability.DEFAULT_SSSS_CAPABILITY; 120 } 121 } 122 123 /** 124 * Static method to get instance. 125 */ getInstance()126 public static PhoneConfigurationManager getInstance() { 127 if (sInstance == null) { 128 Log.wtf(LOG_TAG, "getInstance null"); 129 } 130 131 return sInstance; 132 } 133 134 /** 135 * Handler class to handle callbacks 136 */ 137 private final class ConfigManagerHandler extends Handler { 138 @Override handleMessage(Message msg)139 public void handleMessage(Message msg) { 140 AsyncResult ar; 141 Phone phone = null; 142 switch (msg.what) { 143 case Phone.EVENT_RADIO_AVAILABLE: 144 case Phone.EVENT_RADIO_ON: 145 log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON"); 146 ar = (AsyncResult) msg.obj; 147 if (ar.userObj != null && ar.userObj instanceof Phone) { 148 phone = (Phone) ar.userObj; 149 updatePhoneStatus(phone); 150 } else { 151 // phone is null 152 log("Unable to add phoneStatus to cache. " 153 + "No phone object provided for event " + msg.what); 154 } 155 getStaticPhoneCapability(); 156 break; 157 case EVENT_SWITCH_DSDS_CONFIG_DONE: 158 ar = (AsyncResult) msg.obj; 159 if (ar != null && ar.exception == null) { 160 int numOfLiveModems = msg.arg1; 161 onMultiSimConfigChanged(numOfLiveModems); 162 } else { 163 log(msg.what + " failure. Not switching multi-sim config." + ar.exception); 164 } 165 break; 166 case EVENT_GET_MODEM_STATUS_DONE: 167 ar = (AsyncResult) msg.obj; 168 if (ar != null && ar.exception == null) { 169 int phoneId = msg.arg1; 170 boolean enabled = (boolean) ar.result; 171 // update the cache each time getModemStatus is requested 172 addToPhoneStatusCache(phoneId, enabled); 173 } else { 174 log(msg.what + " failure. Not updating modem status." + ar.exception); 175 } 176 break; 177 case EVENT_GET_PHONE_CAPABILITY_DONE: 178 ar = (AsyncResult) msg.obj; 179 if (ar != null && ar.exception == null) { 180 mStaticCapability = (PhoneCapability) ar.result; 181 notifyCapabilityChanged(); 182 } else { 183 log(msg.what + " failure. Not getting phone capability." + ar.exception); 184 } 185 } 186 } 187 } 188 189 /** 190 * Enable or disable phone 191 * 192 * @param phone which phone to operate on 193 * @param enable true or false 194 * @param result the message to sent back when it's done. 195 */ enablePhone(Phone phone, boolean enable, Message result)196 public void enablePhone(Phone phone, boolean enable, Message result) { 197 if (phone == null) { 198 log("enablePhone failed phone is null"); 199 return; 200 } 201 phone.mCi.enableModem(enable, result); 202 } 203 204 /** 205 * Get phone status (enabled/disabled) 206 * first query cache, if the status is not in cache, 207 * add it to cache and return a default value true (non-blocking). 208 * 209 * @param phone which phone to operate on 210 */ getPhoneStatus(Phone phone)211 public boolean getPhoneStatus(Phone phone) { 212 if (phone == null) { 213 log("getPhoneStatus failed phone is null"); 214 return false; 215 } 216 217 int phoneId = phone.getPhoneId(); 218 219 //use cache if the status has already been updated/queried 220 try { 221 return getPhoneStatusFromCache(phoneId); 222 } catch (NoSuchElementException ex) { 223 // Return true if modem status cannot be retrieved. For most cases, modem status 224 // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not 225 // supported. Modem is always on. 226 //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629 227 return true; 228 } finally { 229 //in either case send an asynchronous request to retrieve the phone status 230 updatePhoneStatus(phone); 231 } 232 } 233 234 /** 235 * Get phone status (enabled/disabled) directly from modem, and use a result Message object 236 * Note: the caller of this method is reponsible to call this in a blocking fashion as well 237 * as read the results and handle the error case. 238 * (In order to be consistent, in error case, we should return default value of true; refer 239 * to #getPhoneStatus method) 240 * 241 * @param phone which phone to operate on 242 * @param result message that will be updated with result 243 */ getPhoneStatusFromModem(Phone phone, Message result)244 public void getPhoneStatusFromModem(Phone phone, Message result) { 245 if (phone == null) { 246 log("getPhoneStatus failed phone is null"); 247 } 248 phone.mCi.getModemStatus(result); 249 } 250 251 /** 252 * return modem status from cache, NoSuchElementException if phoneId not in cache 253 * @param phoneId 254 */ getPhoneStatusFromCache(int phoneId)255 public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException { 256 if (mPhoneStatusMap.containsKey(phoneId)) { 257 return mPhoneStatusMap.get(phoneId); 258 } else { 259 throw new NoSuchElementException("phoneId not found: " + phoneId); 260 } 261 } 262 263 /** 264 * method to call RIL getModemStatus 265 */ updatePhoneStatus(Phone phone)266 private void updatePhoneStatus(Phone phone) { 267 Message result = Message.obtain( 268 mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/); 269 phone.mCi.getModemStatus(result); 270 } 271 272 /** 273 * Add status of the phone to the status HashMap 274 * @param phoneId 275 * @param status 276 */ addToPhoneStatusCache(int phoneId, boolean status)277 public void addToPhoneStatusCache(int phoneId, boolean status) { 278 mPhoneStatusMap.put(phoneId, status); 279 } 280 281 /** 282 * Returns how many phone objects the device supports. 283 */ getPhoneCount()284 public int getPhoneCount() { 285 return mTelephonyManager.getActiveModemCount(); 286 } 287 288 /** 289 * get static overall phone capabilities for all phones. 290 */ getStaticPhoneCapability()291 public synchronized PhoneCapability getStaticPhoneCapability() { 292 if (getDefaultCapability().equals(mStaticCapability)) { 293 log("getStaticPhoneCapability: sending the request for getting PhoneCapability"); 294 Message callback = Message.obtain( 295 mHandler, EVENT_GET_PHONE_CAPABILITY_DONE); 296 mRadioConfig.getPhoneCapability(callback); 297 } 298 log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability); 299 return mStaticCapability; 300 } 301 302 /** 303 * get configuration related status of each phone. 304 */ getCurrentPhoneCapability()305 public PhoneCapability getCurrentPhoneCapability() { 306 return getStaticPhoneCapability(); 307 } 308 getNumberOfModemsWithSimultaneousDataConnections()309 public int getNumberOfModemsWithSimultaneousDataConnections() { 310 return mStaticCapability.maxActiveData; 311 } 312 notifyCapabilityChanged()313 private void notifyCapabilityChanged() { 314 PhoneNotifier notifier = new DefaultPhoneNotifier(mContext); 315 316 notifier.notifyPhoneCapabilityChanged(mStaticCapability); 317 } 318 319 /** 320 * Switch configs to enable multi-sim or switch back to single-sim 321 * @param numOfSims number of active sims we want to switch to 322 */ switchMultiSimConfig(int numOfSims)323 public void switchMultiSimConfig(int numOfSims) { 324 log("switchMultiSimConfig: with numOfSims = " + numOfSims); 325 if (getStaticPhoneCapability().logicalModemList.size() < numOfSims) { 326 log("switchMultiSimConfig: Phone is not capable of enabling " 327 + numOfSims + " sims, exiting!"); 328 return; 329 } 330 if (getPhoneCount() != numOfSims) { 331 log("switchMultiSimConfig: sending the request for switching"); 332 Message callback = Message.obtain( 333 mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/); 334 mRadioConfig.setModemsConfig(numOfSims, callback); 335 } else { 336 log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already " 337 + numOfSims); 338 } 339 } 340 341 /** 342 * Get whether reboot is required or not after making changes to modem configurations. 343 * Return value defaults to true 344 */ isRebootRequiredForModemConfigChange()345 public boolean isRebootRequiredForModemConfigChange() { 346 return mMi.isRebootRequiredForModemConfigChange(); 347 } 348 onMultiSimConfigChanged(int numOfActiveModems)349 private void onMultiSimConfigChanged(int numOfActiveModems) { 350 setMultiSimProperties(numOfActiveModems); 351 352 if (isRebootRequiredForModemConfigChange()) { 353 log("onMultiSimConfigChanged: Rebooting."); 354 PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); 355 pm.reboot("Multi-SIM config changed."); 356 } else { 357 log("onMultiSimConfigChanged: Rebooting is not required."); 358 mMi.notifyPhoneFactoryOnMultiSimConfigChanged(mContext, numOfActiveModems); 359 broadcastMultiSimConfigChange(numOfActiveModems); 360 // Register to RIL service if needed. 361 for (int i = 0; i < mPhones.length; i++) { 362 Phone phone = mPhones[i]; 363 phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(i)); 364 } 365 } 366 } 367 368 /** 369 * Helper method to set system properties for setting multi sim configs, 370 * as well as doing the phone reboot 371 * NOTE: In order to support more than 3 sims, we need to change this method. 372 * @param numOfActiveModems number of active sims 373 */ setMultiSimProperties(int numOfActiveModems)374 private void setMultiSimProperties(int numOfActiveModems) { 375 mMi.setMultiSimProperties(numOfActiveModems); 376 } 377 378 @VisibleForTesting notifyMultiSimConfigChange(int numOfActiveModems)379 public static void notifyMultiSimConfigChange(int numOfActiveModems) { 380 sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems); 381 } 382 383 /** 384 * Register for multi-SIM configuration change, for example if the devices switched from single 385 * SIM to dual-SIM mode. 386 * 387 * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent. 388 */ registerForMultiSimConfigChange(Handler h, int what, Object obj)389 public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) { 390 sMultiSimConfigChangeRegistrants.addUnique(h, what, obj); 391 } 392 393 /** 394 * Unregister for multi-SIM configuration change. 395 */ unregisterForMultiSimConfigChange(Handler h)396 public static void unregisterForMultiSimConfigChange(Handler h) { 397 sMultiSimConfigChangeRegistrants.remove(h); 398 } 399 400 /** 401 * Unregister for all multi-SIM configuration change events. 402 */ unregisterAllMultiSimConfigChangeRegistrants()403 public static void unregisterAllMultiSimConfigChangeRegistrants() { 404 sMultiSimConfigChangeRegistrants.removeAll(); 405 } 406 broadcastMultiSimConfigChange(int numOfActiveModems)407 private void broadcastMultiSimConfigChange(int numOfActiveModems) { 408 log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems); 409 // Notify internal registrants first. 410 notifyMultiSimConfigChange(numOfActiveModems); 411 412 Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED); 413 intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems); 414 mContext.sendBroadcast(intent); 415 } 416 417 /** 418 * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests. 419 * 420 * For example, setting or reading system property are static native methods that can't be 421 * directly mocked. We can mock it by replacing MockableInterface object with a mock instance 422 * in unittest. 423 */ 424 @VisibleForTesting 425 public static class MockableInterface { 426 /** 427 * Wrapper function to decide whether reboot is required for modem config change. 428 */ 429 @VisibleForTesting isRebootRequiredForModemConfigChange()430 public boolean isRebootRequiredForModemConfigChange() { 431 boolean rebootRequired = TelephonyProperties.reboot_on_modem_change().orElse(false); 432 log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired); 433 return rebootRequired; 434 } 435 436 /** 437 * Wrapper function to call setMultiSimProperties. 438 */ 439 @VisibleForTesting setMultiSimProperties(int numOfActiveModems)440 public void setMultiSimProperties(int numOfActiveModems) { 441 String multiSimConfig; 442 switch(numOfActiveModems) { 443 case 3: 444 multiSimConfig = TSTS; 445 break; 446 case 2: 447 multiSimConfig = DSDS; 448 break; 449 default: 450 multiSimConfig = SSSS; 451 } 452 453 log("setMultiSimProperties to " + multiSimConfig); 454 TelephonyProperties.multi_sim_config(multiSimConfig); 455 } 456 457 /** 458 * Wrapper function to call PhoneFactory.onMultiSimConfigChanged. 459 */ 460 @VisibleForTesting notifyPhoneFactoryOnMultiSimConfigChanged( Context context, int numOfActiveModems)461 public void notifyPhoneFactoryOnMultiSimConfigChanged( 462 Context context, int numOfActiveModems) { 463 PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems); 464 } 465 } 466 log(String s)467 private static void log(String s) { 468 Rlog.d(LOG_TAG, s); 469 } 470 } 471