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