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