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