• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Build;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.os.PowerManager;
29 import android.os.RegistrantList;
30 import android.os.SystemProperties;
31 import android.sysprop.TelephonyProperties;
32 import android.telephony.PhoneCapability;
33 import android.telephony.SubscriptionManager;
34 import android.telephony.TelephonyManager;
35 import android.util.Log;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.telephony.Rlog;
39 
40 import java.util.HashMap;
41 import java.util.Map;
42 import java.util.NoSuchElementException;
43 
44 /**
45  * This class manages phone's configuration which defines the potential capability (static) of the
46  * phone and its current activated capability (current).
47  * It gets and monitors static and current phone capability from the modem; send broadcast
48  * if they change, and and sends commands to modem to enable or disable phones.
49  */
50 public class PhoneConfigurationManager {
51     public static final String DSDA = "dsda";
52     public static final String DSDS = "dsds";
53     public static final String TSTS = "tsts";
54     public static final String SSSS = "";
55     private static final String LOG_TAG = "PhoneCfgMgr";
56     private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100;
57     private static final int EVENT_GET_MODEM_STATUS = 101;
58     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
59     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
60 
61     private static PhoneConfigurationManager sInstance = null;
62     private final Context mContext;
63     private PhoneCapability mStaticCapability;
64     private final RadioConfig mRadioConfig;
65     private final Handler mHandler;
66     // mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as
67     // well. That is, the array size can be 2 even if num of active modems is 1.
68     private Phone[] mPhones;
69     private final Map<Integer, Boolean> mPhoneStatusMap;
70     private MockableInterface mMi = new MockableInterface();
71     private TelephonyManager mTelephonyManager;
72     private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
73     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
74     private static final boolean DEBUG = !"user".equals(Build.TYPE);
75     /**
76      * Init method to instantiate the object
77      * Should only be called once.
78      */
init(Context context)79     public static PhoneConfigurationManager init(Context context) {
80         synchronized (PhoneConfigurationManager.class) {
81             if (sInstance == null) {
82                 sInstance = new PhoneConfigurationManager(context);
83             } else {
84                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
85             }
86             return sInstance;
87         }
88     }
89 
90     /**
91      * Constructor.
92      * @param context context needed to send broadcast.
93      */
PhoneConfigurationManager(Context context)94     private PhoneConfigurationManager(Context context) {
95         mContext = context;
96         // TODO: send commands to modem once interface is ready.
97         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
98         //initialize with default, it'll get updated when RADIO is ON/AVAILABLE
99         mStaticCapability = getDefaultCapability();
100         mRadioConfig = RadioConfig.getInstance();
101         mHandler = new ConfigManagerHandler();
102         mPhoneStatusMap = new HashMap<>();
103 
104         notifyCapabilityChanged();
105 
106         mPhones = PhoneFactory.getPhones();
107 
108         for (Phone phone : mPhones) {
109             registerForRadioState(phone);
110         }
111     }
112 
registerForRadioState(Phone phone)113     private void registerForRadioState(Phone phone) {
114         phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
115     }
116 
getDefaultCapability()117     private PhoneCapability getDefaultCapability() {
118         if (getPhoneCount() > 1) {
119             return PhoneCapability.DEFAULT_DSDS_CAPABILITY;
120         } else {
121             return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
122         }
123     }
124 
125     /**
126      * Static method to get instance.
127      */
getInstance()128     public static PhoneConfigurationManager getInstance() {
129         if (sInstance == null) {
130             Log.wtf(LOG_TAG, "getInstance null");
131         }
132 
133         return sInstance;
134     }
135 
136     /**
137      * Handler class to handle callbacks
138      */
139     private final class ConfigManagerHandler extends Handler {
140         @Override
handleMessage(Message msg)141         public void handleMessage(Message msg) {
142             AsyncResult ar;
143             Phone phone = null;
144             switch (msg.what) {
145                 case Phone.EVENT_RADIO_AVAILABLE:
146                 case Phone.EVENT_RADIO_ON:
147                     log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON");
148                     ar = (AsyncResult) msg.obj;
149                     if (ar.userObj != null && ar.userObj instanceof Phone) {
150                         phone = (Phone) ar.userObj;
151                         updatePhoneStatus(phone);
152                     } else {
153                         // phone is null
154                         log("Unable to add phoneStatus to cache. "
155                                 + "No phone object provided for event " + msg.what);
156                     }
157                     getStaticPhoneCapability();
158                     break;
159                 case EVENT_SWITCH_DSDS_CONFIG_DONE:
160                     ar = (AsyncResult) msg.obj;
161                     if (ar != null && ar.exception == null) {
162                         int numOfLiveModems = msg.arg1;
163                         onMultiSimConfigChanged(numOfLiveModems);
164                     } else {
165                         log(msg.what + " failure. Not switching multi-sim config." + ar.exception);
166                     }
167                     break;
168                 case EVENT_GET_MODEM_STATUS_DONE:
169                     ar = (AsyncResult) msg.obj;
170                     if (ar != null && ar.exception == null) {
171                         int phoneId = msg.arg1;
172                         boolean enabled = (boolean) ar.result;
173                         // update the cache each time getModemStatus is requested
174                         addToPhoneStatusCache(phoneId, enabled);
175                     } else {
176                         log(msg.what + " failure. Not updating modem status." + ar.exception);
177                     }
178                     break;
179                 case EVENT_GET_PHONE_CAPABILITY_DONE:
180                     ar = (AsyncResult) msg.obj;
181                     if (ar != null && ar.exception == null) {
182                         mStaticCapability = (PhoneCapability) ar.result;
183                         notifyCapabilityChanged();
184                     } else {
185                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
186                     }
187                     break;
188             }
189         }
190     }
191 
192     /**
193      * Enable or disable phone
194      *
195      * @param phone which phone to operate on
196      * @param enable true or false
197      * @param result the message to sent back when it's done.
198      */
enablePhone(Phone phone, boolean enable, Message result)199     public void enablePhone(Phone phone, boolean enable, Message result) {
200         if (phone == null) {
201             log("enablePhone failed phone is null");
202             return;
203         }
204         phone.mCi.enableModem(enable, result);
205     }
206 
207     /**
208      * Get phone status (enabled/disabled)
209      * first query cache, if the status is not in cache,
210      * add it to cache and return a default value true (non-blocking).
211      *
212      * @param phone which phone to operate on
213      */
getPhoneStatus(Phone phone)214     public boolean getPhoneStatus(Phone phone) {
215         if (phone == null) {
216             log("getPhoneStatus failed phone is null");
217             return false;
218         }
219 
220         int phoneId = phone.getPhoneId();
221 
222         //use cache if the status has already been updated/queried
223         try {
224             return getPhoneStatusFromCache(phoneId);
225         } catch (NoSuchElementException ex) {
226             // Return true if modem status cannot be retrieved. For most cases, modem status
227             // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not
228             // supported. Modem is always on.
229             //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629
230             return true;
231         } finally {
232             //in either case send an asynchronous request to retrieve the phone status
233             updatePhoneStatus(phone);
234         }
235     }
236 
237     /**
238      * Get phone status (enabled/disabled) directly from modem, and use a result Message object
239      * Note: the caller of this method is reponsible to call this in a blocking fashion as well
240      * as read the results and handle the error case.
241      * (In order to be consistent, in error case, we should return default value of true; refer
242      *  to #getPhoneStatus method)
243      *
244      * @param phone which phone to operate on
245      * @param result message that will be updated with result
246      */
getPhoneStatusFromModem(Phone phone, Message result)247     public void getPhoneStatusFromModem(Phone phone, Message result) {
248         if (phone == null) {
249             log("getPhoneStatus failed phone is null");
250         }
251         phone.mCi.getModemStatus(result);
252     }
253 
254     /**
255      * return modem status from cache, NoSuchElementException if phoneId not in cache
256      * @param phoneId
257      */
getPhoneStatusFromCache(int phoneId)258     public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException {
259         if (mPhoneStatusMap.containsKey(phoneId)) {
260             return mPhoneStatusMap.get(phoneId);
261         } else {
262             throw new NoSuchElementException("phoneId not found: " + phoneId);
263         }
264     }
265 
266     /**
267      * method to call RIL getModemStatus
268      */
updatePhoneStatus(Phone phone)269     private void updatePhoneStatus(Phone phone) {
270         Message result = Message.obtain(
271                 mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/);
272         phone.mCi.getModemStatus(result);
273     }
274 
275     /**
276      * Add status of the phone to the status HashMap
277      * @param phoneId
278      * @param status
279      */
addToPhoneStatusCache(int phoneId, boolean status)280     public void addToPhoneStatusCache(int phoneId, boolean status) {
281         mPhoneStatusMap.put(phoneId, status);
282     }
283 
284     /**
285      * Returns how many phone objects the device supports.
286      */
getPhoneCount()287     public int getPhoneCount() {
288         return mTelephonyManager.getActiveModemCount();
289     }
290 
291     /**
292      * get static overall phone capabilities for all phones.
293      */
getStaticPhoneCapability()294     public synchronized PhoneCapability getStaticPhoneCapability() {
295         if (getDefaultCapability().equals(mStaticCapability)) {
296             log("getStaticPhoneCapability: sending the request for getting PhoneCapability");
297             Message callback = Message.obtain(
298                     mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
299             mRadioConfig.getPhoneCapability(callback);
300         }
301         log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
302         return mStaticCapability;
303     }
304 
305     /**
306      * get configuration related status of each phone.
307      */
getCurrentPhoneCapability()308     public PhoneCapability getCurrentPhoneCapability() {
309         return getStaticPhoneCapability();
310     }
311 
getNumberOfModemsWithSimultaneousDataConnections()312     public int getNumberOfModemsWithSimultaneousDataConnections() {
313         return mStaticCapability.getMaxActiveDataSubscriptions();
314     }
315 
notifyCapabilityChanged()316     private void notifyCapabilityChanged() {
317         PhoneNotifier notifier = new DefaultPhoneNotifier(mContext);
318 
319         notifier.notifyPhoneCapabilityChanged(mStaticCapability);
320     }
321 
322     /**
323      * Switch configs to enable multi-sim or switch back to single-sim
324      * @param numOfSims number of active sims we want to switch to
325      */
switchMultiSimConfig(int numOfSims)326     public void switchMultiSimConfig(int numOfSims) {
327         log("switchMultiSimConfig: with numOfSims = " + numOfSims);
328         if (getStaticPhoneCapability().getLogicalModemList().size() < numOfSims) {
329             log("switchMultiSimConfig: Phone is not capable of enabling "
330                     + numOfSims + " sims, exiting!");
331             return;
332         }
333         if (getPhoneCount() != numOfSims) {
334             log("switchMultiSimConfig: sending the request for switching");
335             Message callback = Message.obtain(
336                     mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/);
337             mRadioConfig.setNumOfLiveModems(numOfSims, callback);
338         } else {
339             log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already "
340                     + numOfSims);
341         }
342     }
343 
344     /**
345      * Get whether reboot is required or not after making changes to modem configurations.
346      * Return value defaults to true
347      */
isRebootRequiredForModemConfigChange()348     public boolean isRebootRequiredForModemConfigChange() {
349         return mMi.isRebootRequiredForModemConfigChange();
350     }
351 
onMultiSimConfigChanged(int numOfActiveModems)352     private void onMultiSimConfigChanged(int numOfActiveModems) {
353         int oldNumOfActiveModems = getPhoneCount();
354         setMultiSimProperties(numOfActiveModems);
355 
356         if (isRebootRequiredForModemConfigChange()) {
357             log("onMultiSimConfigChanged: Rebooting.");
358             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
359             pm.reboot("Multi-SIM config changed.");
360         } else {
361             log("onMultiSimConfigChanged: Rebooting is not required.");
362             mMi.notifyPhoneFactoryOnMultiSimConfigChanged(mContext, numOfActiveModems);
363             broadcastMultiSimConfigChange(numOfActiveModems);
364             boolean subInfoCleared = false;
365             // if numOfActiveModems is decreasing, deregister old RILs
366             // eg if we are going from 2 phones to 1 phone, we need to deregister RIL for the
367             // second phone. This loop does nothing if numOfActiveModems is increasing.
368             for (int phoneId = numOfActiveModems; phoneId < oldNumOfActiveModems; phoneId++) {
369                 SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
370                 subInfoCleared = true;
371                 mPhones[phoneId].mCi.onSlotActiveStatusChange(
372                         SubscriptionManager.isValidPhoneId(phoneId));
373             }
374             if (subInfoCleared) {
375                 // This triggers update of default subs. This should be done asap after
376                 // setMultiSimProperties() to avoid (minimize) duration for which default sub can be
377                 // invalid and can map to a non-existent phone.
378                 // If forexample someone calls a TelephonyManager API on default sub after
379                 // setMultiSimProperties() and before onSubscriptionsChanged() below -- they can be
380                 // using an invalid sub, which can map to a non-existent phone and can cause an
381                 // exception (see b/163582235).
382                 MultiSimSettingController.getInstance().onPhoneRemoved();
383             }
384             // old phone objects are not needed now; mPhones can be updated
385             mPhones = PhoneFactory.getPhones();
386             // if numOfActiveModems is increasing, register new RILs
387             // eg if we are going from 1 phone to 2 phones, we need to register RIL for the second
388             // phone. This loop does nothing if numOfActiveModems is decreasing.
389             for (int phoneId = oldNumOfActiveModems; phoneId < numOfActiveModems; phoneId++) {
390                 Phone phone = mPhones[phoneId];
391                 registerForRadioState(phone);
392                 phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(phoneId));
393             }
394 
395             // When the user enables DSDS mode, the default VOICE and SMS subId should be switched
396             // to "No Preference".  Doing so will sync the network/sim settings and telephony.
397             // (see b/198123192)
398             if (numOfActiveModems > oldNumOfActiveModems && numOfActiveModems == 2) {
399                 Log.i(LOG_TAG, " onMultiSimConfigChanged: DSDS mode enabled; "
400                         + "setting VOICE & SMS subId to -1 (No Preference)");
401 
402                 //Set the default VOICE subId to -1 ("No Preference")
403                 SubscriptionController.getInstance().setDefaultVoiceSubId(
404                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
405 
406                 //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042)
407             } else {
408                 Log.i(LOG_TAG,
409                         "onMultiSimConfigChanged: DSDS mode NOT detected.  NOT setting the "
410                                 + "default VOICE and SMS subId to -1 (No Preference)");
411             }
412         }
413     }
414 
415     /**
416      * Helper method to set system properties for setting multi sim configs,
417      * as well as doing the phone reboot
418      * NOTE: In order to support more than 3 sims, we need to change this method.
419      * @param numOfActiveModems number of active sims
420      */
setMultiSimProperties(int numOfActiveModems)421     private void setMultiSimProperties(int numOfActiveModems) {
422         mMi.setMultiSimProperties(numOfActiveModems);
423     }
424 
425     @VisibleForTesting
notifyMultiSimConfigChange(int numOfActiveModems)426     public static void notifyMultiSimConfigChange(int numOfActiveModems) {
427         sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems);
428     }
429 
430     /**
431      * Register for multi-SIM configuration change, for example if the devices switched from single
432      * SIM to dual-SIM mode.
433      *
434      * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent.
435      */
registerForMultiSimConfigChange(Handler h, int what, Object obj)436     public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) {
437         sMultiSimConfigChangeRegistrants.addUnique(h, what, obj);
438     }
439 
440     /**
441      * Unregister for multi-SIM configuration change.
442      */
unregisterForMultiSimConfigChange(Handler h)443     public static void unregisterForMultiSimConfigChange(Handler h) {
444         sMultiSimConfigChangeRegistrants.remove(h);
445     }
446 
447     /**
448      * Unregister for all multi-SIM configuration change events.
449      */
unregisterAllMultiSimConfigChangeRegistrants()450     public static void unregisterAllMultiSimConfigChangeRegistrants() {
451         sMultiSimConfigChangeRegistrants.removeAll();
452     }
453 
broadcastMultiSimConfigChange(int numOfActiveModems)454     private void broadcastMultiSimConfigChange(int numOfActiveModems) {
455         log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems);
456         // Notify internal registrants first.
457         notifyMultiSimConfigChange(numOfActiveModems);
458 
459         Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
460         intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems);
461         mContext.sendBroadcast(intent);
462     }
463     /**
464      * This is invoked from shell commands during CTS testing only.
465      * @return true if the modem service is set successfully, false otherwise.
466      */
setModemService(String serviceName)467     public boolean setModemService(String serviceName) {
468         if (mRadioConfig == null || mPhones[0] == null) {
469             return false;
470         }
471 
472         log("setModemService: " + serviceName);
473         boolean statusRadioConfig = false;
474         boolean statusRil = false;
475         final boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
476 
477         // Check for ALLOW_MOCK_MODEM_PROPERTY on user builds
478         if (isAllowed || DEBUG) {
479             if (serviceName != null) {
480                 statusRadioConfig = mRadioConfig.setModemService(serviceName);
481 
482                 //TODO: consider multi-sim case (b/210073692)
483                 statusRil = mPhones[0].mCi.setModemService(serviceName);
484             } else {
485                 statusRadioConfig = mRadioConfig.setModemService(null);
486 
487                 //TODO: consider multi-sim case
488                 statusRil = mPhones[0].mCi.setModemService(null);
489             }
490 
491             return statusRadioConfig && statusRil;
492         } else {
493             loge("setModemService is not allowed");
494             return false;
495         }
496     }
497 
498      /**
499      * This is invoked from shell commands to query during CTS testing only.
500      * @return the service name of the connected service.
501      */
getModemService()502     public String getModemService() {
503         //TODO: consider multi-sim case
504         if (mPhones[0] == null) {
505             return "";
506         }
507 
508         return mPhones[0].mCi.getModemService();
509     }
510 
511     /**
512      * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests.
513      *
514      * For example, setting or reading system property are static native methods that can't be
515      * directly mocked. We can mock it by replacing MockableInterface object with a mock instance
516      * in unittest.
517      */
518     @VisibleForTesting
519     public static class MockableInterface {
520         /**
521          * Wrapper function to decide whether reboot is required for modem config change.
522          */
523         @VisibleForTesting
isRebootRequiredForModemConfigChange()524         public boolean isRebootRequiredForModemConfigChange() {
525             boolean rebootRequired = TelephonyProperties.reboot_on_modem_change().orElse(false);
526             log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
527             return rebootRequired;
528         }
529 
530         /**
531          * Wrapper function to call setMultiSimProperties.
532          */
533         @VisibleForTesting
setMultiSimProperties(int numOfActiveModems)534         public void setMultiSimProperties(int numOfActiveModems) {
535             String multiSimConfig;
536             switch(numOfActiveModems) {
537                 case 3:
538                     multiSimConfig = TSTS;
539                     break;
540                 case 2:
541                     multiSimConfig = DSDS;
542                     break;
543                 default:
544                     multiSimConfig = SSSS;
545             }
546 
547             log("setMultiSimProperties to " + multiSimConfig);
548             TelephonyProperties.multi_sim_config(multiSimConfig);
549         }
550 
551         /**
552          * Wrapper function to call PhoneFactory.onMultiSimConfigChanged.
553          */
554         @VisibleForTesting
notifyPhoneFactoryOnMultiSimConfigChanged( Context context, int numOfActiveModems)555         public void notifyPhoneFactoryOnMultiSimConfigChanged(
556                 Context context, int numOfActiveModems) {
557             PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
558         }
559     }
560 
log(String s)561     private static void log(String s) {
562         Rlog.d(LOG_TAG, s);
563     }
564 
loge(String s)565     private static void loge(String s) {
566         Rlog.e(LOG_TAG, s);
567     }
568 
loge(String s, Exception ex)569     private static void loge(String s, Exception ex) {
570         Rlog.e(LOG_TAG, s, ex);
571     }
572 }
573