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