| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony; |
| |
| import android.content.Context; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.PowerManager; |
| import android.os.SystemProperties; |
| import android.os.storage.StorageManager; |
| import android.telephony.PhoneCapability; |
| import android.telephony.Rlog; |
| import android.telephony.TelephonyManager; |
| import android.util.Log; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| |
| /** |
| * This class manages phone's configuration which defines the potential capability (static) of the |
| * phone and its current activated capability (current). |
| * It gets and monitors static and current phone capability from the modem; send broadcast |
| * if they change, and and sends commands to modem to enable or disable phones. |
| */ |
| public class PhoneConfigurationManager { |
| public static final String DSDA = "dsda"; |
| public static final String DSDS = "dsds"; |
| public static final String TSTS = "tsts"; |
| public static final String SSSS = ""; |
| private static final String LOG_TAG = "PhoneCfgMgr"; |
| private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100; |
| private static final int EVENT_GET_MODEM_STATUS = 101; |
| private static final int EVENT_GET_MODEM_STATUS_DONE = 102; |
| private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103; |
| |
| private static PhoneConfigurationManager sInstance = null; |
| private final Context mContext; |
| private PhoneCapability mStaticCapability; |
| private final RadioConfig mRadioConfig; |
| private final MainThreadHandler mHandler; |
| private final Phone[] mPhones; |
| private final Map<Integer, Boolean> mPhoneStatusMap; |
| |
| /** |
| * Init method to instantiate the object |
| * Should only be called once. |
| */ |
| public static PhoneConfigurationManager init(Context context) { |
| synchronized (PhoneConfigurationManager.class) { |
| if (sInstance == null) { |
| sInstance = new PhoneConfigurationManager(context); |
| } else { |
| Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); |
| } |
| return sInstance; |
| } |
| } |
| |
| /** |
| * Constructor. |
| * @param context context needed to send broadcast. |
| */ |
| private PhoneConfigurationManager(Context context) { |
| mContext = context; |
| // TODO: send commands to modem once interface is ready. |
| TelephonyManager telephonyManager = new TelephonyManager(context); |
| //initialize with default, it'll get updated when RADIO is ON/AVAILABLE |
| mStaticCapability = getDefaultCapability(); |
| mRadioConfig = RadioConfig.getInstance(mContext); |
| mHandler = new MainThreadHandler(); |
| mPhoneStatusMap = new HashMap<>(); |
| |
| notifyCapabilityChanged(); |
| |
| mPhones = PhoneFactory.getPhones(); |
| if (!StorageManager.inCryptKeeperBounce()) { |
| for (Phone phone : mPhones) { |
| phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone); |
| } |
| } else { |
| for (Phone phone : mPhones) { |
| phone.mCi.registerForOn(mHandler, Phone.EVENT_RADIO_ON, phone); |
| } |
| } |
| } |
| |
| private PhoneCapability getDefaultCapability() { |
| if (getPhoneCount() > 1) { |
| return PhoneCapability.DEFAULT_DSDS_CAPABILITY; |
| } else { |
| return PhoneCapability.DEFAULT_SSSS_CAPABILITY; |
| } |
| } |
| |
| /** |
| * Static method to get instance. |
| */ |
| public static PhoneConfigurationManager getInstance() { |
| if (sInstance == null) { |
| Log.wtf(LOG_TAG, "getInstance null"); |
| } |
| |
| return sInstance; |
| } |
| |
| /** |
| * Handler class to handle callbacks |
| */ |
| private final class MainThreadHandler extends Handler { |
| @Override |
| public void handleMessage(Message msg) { |
| AsyncResult ar; |
| Phone phone = null; |
| switch (msg.what) { |
| case Phone.EVENT_RADIO_AVAILABLE: |
| case Phone.EVENT_RADIO_ON: |
| log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON"); |
| ar = (AsyncResult) msg.obj; |
| if (ar.userObj != null && ar.userObj instanceof Phone) { |
| phone = (Phone) ar.userObj; |
| updatePhoneStatus(phone); |
| } else { |
| // phone is null |
| log("Unable to add phoneStatus to cache. " |
| + "No phone object provided for event " + msg.what); |
| } |
| getStaticPhoneCapability(); |
| break; |
| case EVENT_SWITCH_DSDS_CONFIG_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| int numOfLiveModems = msg.arg1; |
| setMultiSimProperties(numOfLiveModems); |
| } else { |
| log(msg.what + " failure. Not switching multi-sim config." + ar.exception); |
| } |
| break; |
| case EVENT_GET_MODEM_STATUS_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| int phoneId = msg.arg1; |
| boolean enabled = (boolean) ar.result; |
| //update the cache each time getModemStatus is requested |
| addToPhoneStatusCache(phoneId, enabled); |
| } else { |
| log(msg.what + " failure. Not updating modem status." + ar.exception); |
| } |
| break; |
| case EVENT_GET_PHONE_CAPABILITY_DONE: |
| ar = (AsyncResult) msg.obj; |
| if (ar != null && ar.exception == null) { |
| mStaticCapability = (PhoneCapability) ar.result; |
| notifyCapabilityChanged(); |
| } else { |
| log(msg.what + " failure. Not getting phone capability." + ar.exception); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Enable or disable phone |
| * |
| * @param phone which phone to operate on |
| * @param enable true or false |
| * @param result the message to sent back when it's done. |
| */ |
| public void enablePhone(Phone phone, boolean enable, Message result) { |
| if (phone == null) { |
| log("enablePhone failed phone is null"); |
| return; |
| } |
| phone.mCi.enableModem(enable, result); |
| } |
| |
| /** |
| * Get phone status (enabled/disabled) |
| * first query cache, if the status is not in cache, |
| * add it to cache and return a default value true (non-blocking). |
| * |
| * @param phone which phone to operate on |
| */ |
| public boolean getPhoneStatus(Phone phone) { |
| if (phone == null) { |
| log("getPhoneStatus failed phone is null"); |
| return false; |
| } |
| |
| int phoneId = phone.getPhoneId(); |
| |
| //use cache if the status has already been updated/queried |
| try { |
| return getPhoneStatusFromCache(phoneId); |
| } catch (NoSuchElementException ex) { |
| updatePhoneStatus(phone); |
| // Return true if modem status cannot be retrieved. For most cases, modem status |
| // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not |
| // supported. Modem is always on. |
| //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629 |
| return true; |
| } |
| } |
| |
| /** |
| * Get phone status (enabled/disabled) directly from modem, and use a result Message object |
| * Note: the caller of this method is reponsible to call this in a blocking fashion as well |
| * as read the results and handle the error case. |
| * (In order to be consistent, in error case, we should return default value of true; refer |
| * to #getPhoneStatus method) |
| * |
| * @param phone which phone to operate on |
| * @param result message that will be updated with result |
| */ |
| public void getPhoneStatusFromModem(Phone phone, Message result) { |
| if (phone == null) { |
| log("getPhoneStatus failed phone is null"); |
| } |
| phone.mCi.getModemStatus(result); |
| } |
| |
| /** |
| * return modem status from cache, NoSuchElementException if phoneId not in cache |
| * @param phoneId |
| */ |
| public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException { |
| if (mPhoneStatusMap.containsKey(phoneId)) { |
| return mPhoneStatusMap.get(phoneId); |
| } else { |
| throw new NoSuchElementException("phoneId not found: " + phoneId); |
| } |
| } |
| |
| /** |
| * method to call RIL getModemStatus |
| */ |
| private void updatePhoneStatus(Phone phone) { |
| Message result = Message.obtain( |
| mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/); |
| phone.mCi.getModemStatus(result); |
| } |
| |
| /** |
| * Add status of the phone to the status HashMap |
| * @param phoneId |
| * @param status |
| */ |
| public void addToPhoneStatusCache(int phoneId, boolean status) { |
| mPhoneStatusMap.put(phoneId, status); |
| } |
| |
| /** |
| * Returns how many phone objects the device supports. |
| */ |
| public int getPhoneCount() { |
| TelephonyManager tm = new TelephonyManager(mContext); |
| return tm.getPhoneCount(); |
| } |
| |
| /** |
| * get static overall phone capabilities for all phones. |
| */ |
| public synchronized PhoneCapability getStaticPhoneCapability() { |
| if (getDefaultCapability().equals(mStaticCapability)) { |
| log("getStaticPhoneCapability: sending the request for getting PhoneCapability"); |
| Message callback = Message.obtain( |
| mHandler, EVENT_GET_PHONE_CAPABILITY_DONE); |
| mRadioConfig.getPhoneCapability(callback); |
| } |
| return mStaticCapability; |
| } |
| |
| /** |
| * get configuration related status of each phone. |
| */ |
| public PhoneCapability getCurrentPhoneCapability() { |
| return getStaticPhoneCapability(); |
| } |
| |
| public int getNumberOfModemsWithSimultaneousDataConnections() { |
| return mStaticCapability.maxActiveData; |
| } |
| |
| private void notifyCapabilityChanged() { |
| PhoneNotifier notifier = new DefaultPhoneNotifier(); |
| |
| notifier.notifyPhoneCapabilityChanged(mStaticCapability); |
| } |
| |
| /** |
| * Switch configs to enable multi-sim or switch back to single-sim |
| * @param numOfSims number of active sims we want to switch to |
| */ |
| public void switchMultiSimConfig(int numOfSims) { |
| log("switchMultiSimConfig: with numOfSims = " + numOfSims); |
| if (getStaticPhoneCapability().logicalModemList.size() < numOfSims) { |
| log("switchMultiSimConfig: Phone is not capable of enabling " |
| + numOfSims + " sims, exiting!"); |
| return; |
| } |
| if (getPhoneCount() != numOfSims) { |
| log("switchMultiSimConfig: sending the request for switching"); |
| Message callback = Message.obtain( |
| mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/); |
| mRadioConfig.setModemsConfig(numOfSims, callback); |
| } else { |
| log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already " |
| + numOfSims); |
| } |
| } |
| |
| /** |
| * Get whether reboot is required or not after making changes to modem configurations. |
| * Return value defaults to true |
| */ |
| public boolean isRebootRequiredForModemConfigChange() { |
| String rebootRequired = SystemProperties.get( |
| TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE); |
| log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired); |
| return !rebootRequired.equals("false"); |
| } |
| |
| /** |
| * Helper method to set system properties for setting multi sim configs, |
| * as well as doing the phone reboot |
| * NOTE: In order to support more than 3 sims, we need to change this method. |
| * @param numOfSims number of active sims |
| */ |
| private void setMultiSimProperties(int numOfSims) { |
| String finalMultiSimConfig; |
| switch(numOfSims) { |
| case 3: |
| finalMultiSimConfig = TSTS; |
| break; |
| case 2: |
| finalMultiSimConfig = DSDS; |
| break; |
| default: |
| finalMultiSimConfig = SSSS; |
| } |
| |
| SystemProperties.set(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG, finalMultiSimConfig); |
| if (isRebootRequiredForModemConfigChange()) { |
| log("setMultiSimProperties: Rebooting due to switching multi-sim config to " |
| + finalMultiSimConfig); |
| PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); |
| pm.reboot("Switching to " + finalMultiSimConfig); |
| } else { |
| log("setMultiSimProperties: Rebooting is not required to switch multi-sim config to " |
| + finalMultiSimConfig); |
| } |
| } |
| |
| private static void log(String s) { |
| Rlog.d(LOG_TAG, s); |
| } |
| } |