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