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