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