• 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.annotation.NonNull;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.os.AsyncResult;
26 import android.os.Build;
27 import android.os.Handler;
28 import android.os.Message;
29 import android.os.PowerManager;
30 import android.os.RegistrantList;
31 import android.os.SystemProperties;
32 import android.provider.DeviceConfig;
33 import android.sysprop.TelephonyProperties;
34 import android.telephony.PhoneCapability;
35 import android.telephony.SubscriptionManager;
36 import android.telephony.TelephonyManager;
37 import android.telephony.TelephonyRegistryManager;
38 import android.text.TextUtils;
39 import android.util.Log;
40 
41 import com.android.internal.annotations.VisibleForTesting;
42 import com.android.internal.telephony.flags.FeatureFlags;
43 import com.android.internal.telephony.subscription.SubscriptionManagerService;
44 import com.android.telephony.Rlog;
45 
46 import java.util.HashMap;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.NoSuchElementException;
51 import java.util.Optional;
52 import java.util.Set;
53 import java.util.concurrent.CopyOnWriteArraySet;
54 import java.util.function.Consumer;
55 import java.util.stream.Collectors;
56 
57 /**
58  * This class manages phone's configuration which defines the potential capability (static) of the
59  * phone and its current activated capability (current).
60  * It gets and monitors static and current phone capability from the modem; send broadcast
61  * if they change, and sends commands to modem to enable or disable phones.
62  */
63 public class PhoneConfigurationManager {
64     public static final String DSDA = "dsda";
65     public static final String DSDS = "dsds";
66     public static final String TSTS = "tsts";
67     public static final String SSSS = "";
68     /** DeviceConfig key for whether Virtual DSDA is enabled. */
69     private static final String KEY_ENABLE_VIRTUAL_DSDA = "enable_virtual_dsda";
70     private static final String LOG_TAG = "PhoneCfgMgr";
71     private static final int EVENT_SWITCH_DSDS_CONFIG_DONE = 100;
72     private static final int EVENT_GET_MODEM_STATUS = 101;
73     private static final int EVENT_GET_MODEM_STATUS_DONE = 102;
74     private static final int EVENT_GET_PHONE_CAPABILITY_DONE = 103;
75     private static final int EVENT_DEVICE_CONFIG_CHANGED = 104;
76     private static final int EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE = 105;
77     private static final int EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED = 106;
78 
79     /**
80      * Listener interface for events related to the {@link PhoneConfigurationManager} which should
81      * be reported to the {@link SimultaneousCallingTracker}.
82      */
83     public interface Listener {
onPhoneCapabilityChanged()84         public void onPhoneCapabilityChanged();
onDeviceConfigChanged()85         public void onDeviceConfigChanged();
86     }
87 
88     /**
89      * Base listener implementation.
90      */
91     public abstract static class ListenerBase implements Listener {
92         @Override
onPhoneCapabilityChanged()93         public void onPhoneCapabilityChanged() {}
94         @Override
onDeviceConfigChanged()95         public void onDeviceConfigChanged() {}
96     }
97 
98 
99     private static PhoneConfigurationManager sInstance = null;
100     private final Context mContext;
101     // Static capability retrieved from the modem - may be null in the case where no info has been
102     // retrieved yet.
103     private PhoneCapability mStaticCapability = null;
104     private final Set<Integer> mSlotsSupportingSimultaneousCellularCalls = new HashSet<>(3);
105     private final Set<Integer> mSubIdsSupportingSimultaneousCellularCalls = new HashSet<>(3);
106     private final HashSet<Consumer<Set<Integer>>> mSimultaneousCellularCallingListeners =
107             new HashSet<>(1);
108     private final RadioConfig mRadioConfig;
109     private final Handler mHandler;
110     // mPhones is obtained from PhoneFactory and can have phones corresponding to inactive modems as
111     // well. That is, the array size can be 2 even if num of active modems is 1.
112     private Phone[] mPhones;
113     private final Map<Integer, Boolean> mPhoneStatusMap;
114     private MockableInterface mMi = new MockableInterface();
115     private TelephonyManager mTelephonyManager;
116 
117     /** Feature flags */
118     @NonNull
119     private final FeatureFlags mFeatureFlags;
120     private final DefaultPhoneNotifier mNotifier;
121     public Set<Listener> mListeners = new CopyOnWriteArraySet<>();
122 
123     /**
124      * True if 'Virtual DSDA' i.e., in-call IMS connectivity on both subs with only single logical
125      * modem, is enabled.
126      */
127     private boolean mVirtualDsdaEnabled;
128     private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();
129     private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
130     private static final String BOOT_ALLOW_MOCK_MODEM_PROPERTY = "ro.boot.radio.allow_mock_modem";
131     private static final boolean DEBUG = !"user".equals(Build.TYPE);
132     /**
133      * Init method to instantiate the object
134      * Should only be called once.
135      */
init(Context context, @NonNull FeatureFlags featureFlags)136     public static PhoneConfigurationManager init(Context context,
137             @NonNull FeatureFlags featureFlags) {
138         synchronized (PhoneConfigurationManager.class) {
139             if (sInstance == null) {
140                 sInstance = new PhoneConfigurationManager(context, featureFlags);
141             } else {
142                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
143             }
144             return sInstance;
145         }
146     }
147 
148     /**
149      * Constructor.
150      * @param context context needed to send broadcast.
151      */
PhoneConfigurationManager(Context context, @NonNull FeatureFlags featureFlags)152     private PhoneConfigurationManager(Context context, @NonNull FeatureFlags featureFlags) {
153         mContext = context;
154         mFeatureFlags = featureFlags;
155         // TODO: send commands to modem once interface is ready.
156         mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
157         mRadioConfig = RadioConfig.getInstance();
158         mHandler = new ConfigManagerHandler();
159         mPhoneStatusMap = new HashMap<>();
160         mVirtualDsdaEnabled = DeviceConfig.getBoolean(
161                 DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false);
162         mNotifier = new DefaultPhoneNotifier(mContext, mFeatureFlags);
163         DeviceConfig.addOnPropertiesChangedListener(
164                 DeviceConfig.NAMESPACE_TELEPHONY, Runnable::run,
165                 properties -> {
166                     if (TextUtils.equals(DeviceConfig.NAMESPACE_TELEPHONY,
167                             properties.getNamespace())) {
168                         mHandler.sendEmptyMessage(EVENT_DEVICE_CONFIG_CHANGED);
169                     }
170                 });
171 
172         notifyCapabilityChanged();
173 
174         mPhones = PhoneFactory.getPhones();
175 
176         for (Phone phone : mPhones) {
177             registerForRadioState(phone);
178         }
179     }
180 
181     /**
182      * Assign a listener to be notified of state changes.
183      *
184      * @param listener A listener.
185      */
addListener(Listener listener)186     public void addListener(Listener listener) {
187         mListeners.add(listener);
188     }
189 
190     /**
191      * Removes a listener.
192      *
193      * @param listener A listener.
194      */
removeListener(Listener listener)195     public final void removeListener(Listener listener) {
196         mListeners.remove(listener);
197     }
198 
199     /**
200      * Updates the mapping between the slot IDs that support simultaneous calling and the
201      * associated sub IDs as well as notifies listeners.
202      */
updateSimultaneousSubIdsFromPhoneIdMappingAndNotify()203     private void updateSimultaneousSubIdsFromPhoneIdMappingAndNotify() {
204         if (!mFeatureFlags.simultaneousCallingIndications()) return;
205         Set<Integer> slotCandidates = mSlotsSupportingSimultaneousCellularCalls.stream()
206                 .map(i -> mPhones[i].getSubId())
207                 .filter(i ->i > SubscriptionManager.INVALID_SUBSCRIPTION_ID)
208                         .collect(Collectors.toSet());
209         if (mSubIdsSupportingSimultaneousCellularCalls.equals(slotCandidates))  return;
210         log("updateSimultaneousSubIdsFromPhoneIdMapping update: "
211                 + mSubIdsSupportingSimultaneousCellularCalls + " -> " + slotCandidates);
212         mSubIdsSupportingSimultaneousCellularCalls.clear();
213         mSubIdsSupportingSimultaneousCellularCalls.addAll(slotCandidates);
214         mNotifier.notifySimultaneousCellularCallingSubscriptionsChanged(
215                 mSubIdsSupportingSimultaneousCellularCalls);
216     }
217 
registerForRadioState(Phone phone)218     private void registerForRadioState(Phone phone) {
219         phone.mCi.registerForAvailable(mHandler, Phone.EVENT_RADIO_AVAILABLE, phone);
220     }
221 
getDefaultCapability()222     private PhoneCapability getDefaultCapability() {
223         if (getPhoneCount() > 1) {
224             return PhoneCapability.DEFAULT_DSDS_CAPABILITY;
225         } else {
226             return PhoneCapability.DEFAULT_SSSS_CAPABILITY;
227         }
228     }
229 
230     /**
231      * If virtual DSDA is enabled for this UE, then increase maxActiveVoiceSubscriptions to 2.
232      */
maybeOverrideMaxActiveVoiceSubscriptions( final PhoneCapability staticCapability)233     private PhoneCapability maybeOverrideMaxActiveVoiceSubscriptions(
234             final PhoneCapability staticCapability) {
235         boolean isVDsdaEnabled = staticCapability.getLogicalModemList().size() > 1
236                 && mVirtualDsdaEnabled;
237         boolean isBkwdCompatDsdaEnabled = mFeatureFlags.simultaneousCallingIndications()
238                 && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
239         if (isVDsdaEnabled || isBkwdCompatDsdaEnabled) {
240             // Since we already initialized maxActiveVoiceSubscriptions to the count the
241             // modem is capable of, we are only able to increase that count via this method. We do
242             // not allow a decrease of maxActiveVoiceSubscriptions:
243             int updatedMaxActiveVoiceSubscriptions =
244                     Math.max(staticCapability.getMaxActiveVoiceSubscriptions(), 2);
245             return new PhoneCapability.Builder(staticCapability)
246                     .setMaxActiveVoiceSubscriptions(updatedMaxActiveVoiceSubscriptions)
247                     .build();
248         } else {
249             return staticCapability;
250         }
251     }
252 
maybeEnableCellularDSDASupport()253     private void maybeEnableCellularDSDASupport() {
254         boolean bkwdsCompatDsda = mFeatureFlags.simultaneousCallingIndications()
255                 && getPhoneCount() > 1
256                 && mMi.getMultiSimProperty().orElse(SSSS).equals(DSDA);
257         boolean halSupportSimulCalling = mRadioConfig != null
258                 && mRadioConfig.getRadioConfigProxy(null).getVersion().greaterOrEqual(
259                         RIL.RADIO_HAL_VERSION_2_2)
260                 && getPhoneCount() > 1
261                 && getCellularStaticPhoneCapability().getMaxActiveVoiceSubscriptions() > 1;
262         // Register for simultaneous calling support changes in the modem if the HAL supports it
263         if (halSupportSimulCalling) {
264             updateSimultaneousCallingSupport();
265             mRadioConfig.registerForSimultaneousCallingSupportStatusChanged(mHandler,
266                     EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED, null);
267         } else if (bkwdsCompatDsda) {
268             // For older devices that only declare that they support DSDA via modem config,
269             // set DSDA as capable now statically.
270             log("DSDA modem config detected - setting DSDA enabled");
271             for (Phone p : mPhones) {
272                 mSlotsSupportingSimultaneousCellularCalls.add(p.getPhoneId());
273             }
274             updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
275             notifySimultaneousCellularCallingSlotsChanged();
276         }
277         // Register for subId updates to notify listeners when simultaneous calling is configured
278         if (mFeatureFlags.simultaneousCallingIndications()
279                 && (bkwdsCompatDsda || halSupportSimulCalling)) {
280             mContext.getSystemService(TelephonyRegistryManager.class)
281                     .addOnSubscriptionsChangedListener(
282                             new SubscriptionManager.OnSubscriptionsChangedListener() {
283                                 @Override
284                                 public void onSubscriptionsChanged() {
285                                     updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
286                                 }
287                             }, mHandler::post);
288         }
289     }
290 
291     /**
292      * Static method to get instance.
293      */
getInstance()294     public static PhoneConfigurationManager getInstance() {
295         if (sInstance == null) {
296             Log.wtf(LOG_TAG, "getInstance null");
297         }
298 
299         return sInstance;
300     }
301 
302     /**
303      * Handler class to handle callbacks
304      */
305     private final class ConfigManagerHandler extends Handler {
306         @Override
handleMessage(Message msg)307         public void handleMessage(Message msg) {
308             AsyncResult ar;
309             Phone phone = null;
310             switch (msg.what) {
311                 case Phone.EVENT_RADIO_AVAILABLE:
312                 case Phone.EVENT_RADIO_ON:
313                     log("Received EVENT_RADIO_AVAILABLE/EVENT_RADIO_ON");
314                     ar = (AsyncResult) msg.obj;
315                     if (ar.userObj != null && ar.userObj instanceof Phone) {
316                         phone = (Phone) ar.userObj;
317                         updatePhoneStatus(phone);
318                     } else {
319                         // phone is null
320                         log("Unable to add phoneStatus to cache. "
321                                 + "No phone object provided for event " + msg.what);
322                     }
323                     updateRadioCapability();
324                     break;
325                 case EVENT_SWITCH_DSDS_CONFIG_DONE:
326                     ar = (AsyncResult) msg.obj;
327                     if (ar != null && ar.exception == null) {
328                         int numOfLiveModems = msg.arg1;
329                         onMultiSimConfigChanged(numOfLiveModems);
330                     } else {
331                         log(msg.what + " failure. Not switching multi-sim config." + ar.exception);
332                     }
333                     break;
334                 case EVENT_GET_MODEM_STATUS_DONE:
335                     ar = (AsyncResult) msg.obj;
336                     if (ar != null && ar.exception == null) {
337                         int phoneId = msg.arg1;
338                         boolean enabled = (boolean) ar.result;
339                         // update the cache each time getModemStatus is requested
340                         addToPhoneStatusCache(phoneId, enabled);
341                     } else {
342                         log(msg.what + " failure. Not updating modem status." + ar.exception);
343                     }
344                     break;
345                 case EVENT_GET_PHONE_CAPABILITY_DONE:
346                     ar = (AsyncResult) msg.obj;
347                     if (ar != null && ar.exception == null) {
348                         setStaticPhoneCapability((PhoneCapability) ar.result);
349                         notifyCapabilityChanged();
350                         for (Listener l : mListeners) {
351                             l.onPhoneCapabilityChanged();
352                         }
353                         maybeEnableCellularDSDASupport();
354                     } else {
355                         log(msg.what + " failure. Not getting phone capability." + ar.exception);
356                     }
357                     break;
358                 case EVENT_DEVICE_CONFIG_CHANGED:
359                     boolean isVirtualDsdaEnabled = DeviceConfig.getBoolean(
360                             DeviceConfig.NAMESPACE_TELEPHONY, KEY_ENABLE_VIRTUAL_DSDA, false);
361                     if (isVirtualDsdaEnabled != mVirtualDsdaEnabled) {
362                         log("EVENT_DEVICE_CONFIG_CHANGED: from " + mVirtualDsdaEnabled + " to "
363                                 + isVirtualDsdaEnabled);
364                         mVirtualDsdaEnabled = isVirtualDsdaEnabled;
365                         for (Listener l : mListeners) {
366                             l.onDeviceConfigChanged();
367                         }
368                     }
369                     break;
370                 case EVENT_SIMULTANEOUS_CALLING_SUPPORT_CHANGED:
371                 case EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE:
372                     log("Received EVENT_SLOTS_SUPPORTING_SIMULTANEOUS_CALL_CHANGED/DONE");
373                     if (getPhoneCount() < 2) {
374                         if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) {
375                             mSlotsSupportingSimultaneousCellularCalls.clear();
376                         }
377                         break;
378                     }
379                     ar = (AsyncResult) msg.obj;
380                     if (ar != null && ar.exception == null) {
381                         List<Integer> returnedArrayList = (List<Integer>) ar.result;
382                         if (!mSlotsSupportingSimultaneousCellularCalls.isEmpty()) {
383                             mSlotsSupportingSimultaneousCellularCalls.clear();
384                         }
385                         int maxValidPhoneSlot = getPhoneCount() - 1;
386                         for (int i : returnedArrayList) {
387                             if (i < 0 || i > maxValidPhoneSlot) {
388                                 loge("Invalid slot supporting DSDA =" + i + ". Disabling DSDA.");
389                                 mSlotsSupportingSimultaneousCellularCalls.clear();
390                                 break;
391                             }
392                             mSlotsSupportingSimultaneousCellularCalls.add(i);
393                         }
394                         // Ensure the slots supporting cellular DSDA does not exceed the phone count
395                         if (mSlotsSupportingSimultaneousCellularCalls.size() > getPhoneCount()) {
396                             loge("Invalid size of DSDA slots. Disabling cellular DSDA.");
397                             mSlotsSupportingSimultaneousCellularCalls.clear();
398                         }
399                     } else {
400                         log(msg.what + " failure. Not getting logical slots that support "
401                                 + "simultaneous calling." + ar.exception);
402                         mSlotsSupportingSimultaneousCellularCalls.clear();
403                     }
404                     if (mFeatureFlags.simultaneousCallingIndications()) {
405                         updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
406                         notifySimultaneousCellularCallingSlotsChanged();
407                     }
408                     break;
409                 default:
410                     log("Unknown event: " + msg.what);
411             }
412         }
413     }
414 
415     /**
416      * Enable or disable phone
417      *
418      * @param phone which phone to operate on
419      * @param enable true or false
420      * @param result the message to sent back when it's done.
421      */
enablePhone(Phone phone, boolean enable, Message result)422     public void enablePhone(Phone phone, boolean enable, Message result) {
423         if (phone == null) {
424             log("enablePhone failed phone is null");
425             return;
426         }
427         phone.mCi.enableModem(enable, result);
428     }
429 
430     /**
431      * Get phone status (enabled/disabled)
432      * first query cache, if the status is not in cache,
433      * add it to cache and return a default value true (non-blocking).
434      *
435      * @param phone which phone to operate on
436      */
getPhoneStatus(Phone phone)437     public boolean getPhoneStatus(Phone phone) {
438         if (phone == null) {
439             log("getPhoneStatus failed phone is null");
440             return false;
441         }
442 
443         int phoneId = phone.getPhoneId();
444 
445         //use cache if the status has already been updated/queried
446         try {
447             return getPhoneStatusFromCache(phoneId);
448         } catch (NoSuchElementException ex) {
449             // Return true if modem status cannot be retrieved. For most cases, modem status
450             // is on. And for older version modems, GET_MODEM_STATUS and disable modem are not
451             // supported. Modem is always on.
452             //TODO: this should be fixed in R to support a third status UNKNOWN b/131631629
453             return true;
454         } finally {
455             //in either case send an asynchronous request to retrieve the phone status
456             updatePhoneStatus(phone);
457         }
458     }
459 
460     /**
461      * Get phone status (enabled/disabled) directly from modem, and use a result Message object
462      * Note: the caller of this method is reponsible to call this in a blocking fashion as well
463      * as read the results and handle the error case.
464      * (In order to be consistent, in error case, we should return default value of true; refer
465      *  to #getPhoneStatus method)
466      *
467      * @param phone which phone to operate on
468      * @param result message that will be updated with result
469      */
getPhoneStatusFromModem(Phone phone, Message result)470     public void getPhoneStatusFromModem(Phone phone, Message result) {
471         if (phone == null) {
472             log("getPhoneStatus failed phone is null");
473         }
474         phone.mCi.getModemStatus(result);
475     }
476 
477     /**
478      * return modem status from cache, NoSuchElementException if phoneId not in cache
479      * @param phoneId
480      */
getPhoneStatusFromCache(int phoneId)481     public boolean getPhoneStatusFromCache(int phoneId) throws NoSuchElementException {
482         if (mPhoneStatusMap.containsKey(phoneId)) {
483             return mPhoneStatusMap.get(phoneId);
484         } else {
485             throw new NoSuchElementException("phoneId not found: " + phoneId);
486         }
487     }
488 
489     /**
490      * method to call RIL getModemStatus
491      */
updatePhoneStatus(Phone phone)492     private void updatePhoneStatus(Phone phone) {
493         Message result = Message.obtain(
494                 mHandler, EVENT_GET_MODEM_STATUS_DONE, phone.getPhoneId(), 0 /**dummy arg*/);
495         phone.mCi.getModemStatus(result);
496     }
497 
498     /**
499      * Add status of the phone to the status HashMap
500      * @param phoneId
501      * @param status
502      */
addToPhoneStatusCache(int phoneId, boolean status)503     public void addToPhoneStatusCache(int phoneId, boolean status) {
504         mPhoneStatusMap.put(phoneId, status);
505     }
506 
507     /**
508      * Returns how many phone objects the device supports.
509      */
getPhoneCount()510     public int getPhoneCount() {
511         return mTelephonyManager.getActiveModemCount();
512     }
513 
514     /**
515      * @return The updated list of logical slots that support simultaneous cellular calling from the
516      * modem based on current network conditions.
517      */
getSlotsSupportingSimultaneousCellularCalls()518     public Set<Integer> getSlotsSupportingSimultaneousCellularCalls() {
519         return mSlotsSupportingSimultaneousCellularCalls;
520     }
521 
522     /**
523      * Get the current the list of logical slots supporting simultaneous cellular calling from the
524      * modem based on current network conditions.
525      */
526     @VisibleForTesting
updateSimultaneousCallingSupport()527     public void updateSimultaneousCallingSupport() {
528         log("updateSimultaneousCallingSupport: sending the request for "
529                 + "getting the list of logical slots supporting simultaneous cellular calling");
530         Message callback = Message.obtain(
531                 mHandler, EVENT_GET_SIMULTANEOUS_CALLING_SUPPORT_DONE);
532         mRadioConfig.updateSimultaneousCallingSupport(callback);
533         log("updateSimultaneousCallingSupport: "
534                 + "mSlotsSupportingSimultaneousCellularCalls = " +
535                 mSlotsSupportingSimultaneousCellularCalls);
536     }
537 
538     /**
539      * @return static overall phone capabilities for all phones, including voice overrides.
540      */
getStaticPhoneCapability()541     public synchronized PhoneCapability getStaticPhoneCapability() {
542         boolean isDefault = mStaticCapability == null;
543         PhoneCapability caps = isDefault ? getDefaultCapability() : mStaticCapability;
544         caps = maybeOverrideMaxActiveVoiceSubscriptions(caps);
545         log("getStaticPhoneCapability: isDefault=" + isDefault + ", caps=" + caps);
546         return caps;
547     }
548 
549     /**
550      * @return untouched capabilities returned from the modem
551      */
getCellularStaticPhoneCapability()552     private synchronized PhoneCapability getCellularStaticPhoneCapability() {
553         log("getCellularStaticPhoneCapability: mStaticCapability " + mStaticCapability);
554         return mStaticCapability;
555     }
556 
557     /**
558      * Caches the static PhoneCapability returned by the modem
559      */
setStaticPhoneCapability(PhoneCapability capability)560     public synchronized void setStaticPhoneCapability(PhoneCapability capability) {
561         log("setStaticPhoneCapability: mStaticCapability " + capability);
562         mStaticCapability = capability;
563     }
564 
565     /**
566      * Query the modem to return its static PhoneCapability and cache it
567      */
568     @VisibleForTesting
updateRadioCapability()569     public void updateRadioCapability() {
570         log("updateRadioCapability: sending the request for getting PhoneCapability");
571         Message callback = Message.obtain(mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
572         mRadioConfig.getPhoneCapability(callback);
573     }
574 
575     /**
576      * get configuration related status of each phone.
577      */
getCurrentPhoneCapability()578     public PhoneCapability getCurrentPhoneCapability() {
579         return getStaticPhoneCapability();
580     }
581 
getNumberOfModemsWithSimultaneousDataConnections()582     public int getNumberOfModemsWithSimultaneousDataConnections() {
583         return getStaticPhoneCapability().getMaxActiveDataSubscriptions();
584     }
585 
getNumberOfModemsWithSimultaneousVoiceConnections()586     public int getNumberOfModemsWithSimultaneousVoiceConnections() {
587         return getStaticPhoneCapability().getMaxActiveVoiceSubscriptions();
588     }
589 
isVirtualDsdaEnabled()590     public boolean isVirtualDsdaEnabled() {
591         return mVirtualDsdaEnabled;
592     }
593 
594     /**
595      * Register to listen to changes in the Phone slots that support simultaneous calling.
596      * @param consumer A consumer that will be used to consume the new slots supporting simultaneous
597      *                 cellular calling when it changes.
598      */
registerForSimultaneousCellularCallingSlotsChanged( Consumer<Set<Integer>> consumer)599     public void registerForSimultaneousCellularCallingSlotsChanged(
600             Consumer<Set<Integer>> consumer) {
601         mSimultaneousCellularCallingListeners.add(consumer);
602     }
603 
notifySimultaneousCellularCallingSlotsChanged()604     private void notifySimultaneousCellularCallingSlotsChanged() {
605         log("notifying listeners of changes to simultaneous cellular calling - new state:"
606                 + mSlotsSupportingSimultaneousCellularCalls);
607         for (Consumer<Set<Integer>> consumer : mSimultaneousCellularCallingListeners) {
608             try {
609                 consumer.accept(new HashSet<>(mSlotsSupportingSimultaneousCellularCalls));
610             } catch (Exception e) {
611                 log("Unexpected Exception encountered when notifying listener: " + e);
612             }
613         }
614     }
615 
notifyCapabilityChanged()616     private void notifyCapabilityChanged() {
617         mNotifier.notifyPhoneCapabilityChanged(getStaticPhoneCapability());
618     }
619 
620     /**
621      * Switch configs to enable multi-sim or switch back to single-sim
622      * @param numOfSims number of active sims we want to switch to
623      */
switchMultiSimConfig(int numOfSims)624     public void switchMultiSimConfig(int numOfSims) {
625         log("switchMultiSimConfig: with numOfSims = " + numOfSims);
626         if (getStaticPhoneCapability().getLogicalModemList().size() < numOfSims) {
627             log("switchMultiSimConfig: Phone is not capable of enabling "
628                     + numOfSims + " sims, exiting!");
629             return;
630         }
631         if (getPhoneCount() != numOfSims) {
632             log("switchMultiSimConfig: sending the request for switching");
633             Message callback = Message.obtain(
634                     mHandler, EVENT_SWITCH_DSDS_CONFIG_DONE, numOfSims, 0 /**dummy arg*/);
635             mRadioConfig.setNumOfLiveModems(numOfSims, callback);
636         } else {
637             log("switchMultiSimConfig: No need to switch. getNumOfActiveSims is already "
638                     + numOfSims);
639         }
640     }
641 
642     /**
643      * Get whether reboot is required or not after making changes to modem configurations.
644      * Return value defaults to true
645      */
isRebootRequiredForModemConfigChange()646     public boolean isRebootRequiredForModemConfigChange() {
647         return mMi.isRebootRequiredForModemConfigChange();
648     }
649 
onMultiSimConfigChanged(int numOfActiveModems)650     private void onMultiSimConfigChanged(int numOfActiveModems) {
651         int oldNumOfActiveModems = getPhoneCount();
652         setMultiSimProperties(numOfActiveModems);
653 
654         if (isRebootRequiredForModemConfigChange()) {
655             log("onMultiSimConfigChanged: Rebooting.");
656             PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
657             pm.reboot("Multi-SIM config changed.");
658         } else {
659             log("onMultiSimConfigChanged: Rebooting is not required.");
660             mMi.notifyPhoneFactoryOnMultiSimConfigChanged(mContext, numOfActiveModems);
661             broadcastMultiSimConfigChange(numOfActiveModems);
662             boolean subInfoCleared = false;
663             // if numOfActiveModems is decreasing, deregister old RILs
664             // eg if we are going from 2 phones to 1 phone, we need to deregister RIL for the
665             // second phone. This loop does nothing if numOfActiveModems is increasing.
666             for (int phoneId = numOfActiveModems; phoneId < oldNumOfActiveModems; phoneId++) {
667                 SubscriptionManagerService.getInstance().markSubscriptionsInactive(phoneId);
668                 subInfoCleared = true;
669                 mPhones[phoneId].mCi.onSlotActiveStatusChange(
670                         SubscriptionManager.isValidPhoneId(phoneId));
671             }
672             if (subInfoCleared) {
673                 // This triggers update of default subs. This should be done asap after
674                 // setMultiSimProperties() to avoid (minimize) duration for which default sub can be
675                 // invalid and can map to a non-existent phone.
676                 // If forexample someone calls a TelephonyManager API on default sub after
677                 // setMultiSimProperties() and before onSubscriptionsChanged() below -- they can be
678                 // using an invalid sub, which can map to a non-existent phone and can cause an
679                 // exception (see b/163582235).
680                 MultiSimSettingController.getInstance().onPhoneRemoved();
681             }
682             // old phone objects are not needed now; mPhones can be updated
683             mPhones = PhoneFactory.getPhones();
684             // if numOfActiveModems is increasing, register new RILs
685             // eg if we are going from 1 phone to 2 phones, we need to register RIL for the second
686             // phone. This loop does nothing if numOfActiveModems is decreasing.
687             for (int phoneId = oldNumOfActiveModems; phoneId < numOfActiveModems; phoneId++) {
688                 Phone phone = mPhones[phoneId];
689                 registerForRadioState(phone);
690                 phone.mCi.onSlotActiveStatusChange(SubscriptionManager.isValidPhoneId(phoneId));
691             }
692 
693             if (numOfActiveModems > 1) {
694                 // Check if cellular DSDA is supported. If it is, then send a request to the
695                 // modem to refresh the list of SIM slots that currently support DSDA based on
696                 // current network conditions
697                 maybeEnableCellularDSDASupport();
698             } else {
699                 // The number of active modems is 0 or 1, disable cellular DSDA:
700                 mSlotsSupportingSimultaneousCellularCalls.clear();
701                 if (mFeatureFlags.simultaneousCallingIndications()) {
702                     updateSimultaneousSubIdsFromPhoneIdMappingAndNotify();
703                     notifySimultaneousCellularCallingSlotsChanged();
704                 }
705             }
706 
707             // When the user enables DSDS mode, the default VOICE and SMS subId should be switched
708             // to "No Preference".  Doing so will sync the network/sim settings and telephony.
709             // (see b/198123192)
710             if (numOfActiveModems > oldNumOfActiveModems && numOfActiveModems == 2) {
711                 Log.i(LOG_TAG, " onMultiSimConfigChanged: DSDS mode enabled; "
712                         + "setting VOICE & SMS subId to -1 (No Preference)");
713 
714                 //Set the default VOICE subId to -1 ("No Preference")
715                 SubscriptionManagerService.getInstance().setDefaultVoiceSubId(
716                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
717 
718                 //TODO:: Set the default SMS sub to "No Preference". Tracking this bug (b/227386042)
719             } else {
720                 Log.i(LOG_TAG,
721                         "onMultiSimConfigChanged: DSDS mode NOT detected.  NOT setting the "
722                                 + "default VOICE and SMS subId to -1 (No Preference)");
723             }
724         }
725     }
726 
727     /**
728      * Helper method to set system properties for setting multi sim configs,
729      * as well as doing the phone reboot
730      * NOTE: In order to support more than 3 sims, we need to change this method.
731      * @param numOfActiveModems number of active sims
732      */
setMultiSimProperties(int numOfActiveModems)733     private void setMultiSimProperties(int numOfActiveModems) {
734         mMi.setMultiSimProperties(numOfActiveModems);
735     }
736 
737     @VisibleForTesting
notifyMultiSimConfigChange(int numOfActiveModems)738     public static void notifyMultiSimConfigChange(int numOfActiveModems) {
739         sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems);
740     }
741 
742     /**
743      * Register for multi-SIM configuration change, for example if the devices switched from single
744      * SIM to dual-SIM mode.
745      *
746      * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent.
747      */
registerForMultiSimConfigChange(Handler h, int what, Object obj)748     public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) {
749         sMultiSimConfigChangeRegistrants.addUnique(h, what, obj);
750     }
751 
752     /**
753      * Unregister for multi-SIM configuration change.
754      */
unregisterForMultiSimConfigChange(Handler h)755     public static void unregisterForMultiSimConfigChange(Handler h) {
756         sMultiSimConfigChangeRegistrants.remove(h);
757     }
758 
759     /**
760      * Unregister for all multi-SIM configuration change events.
761      */
unregisterAllMultiSimConfigChangeRegistrants()762     public static void unregisterAllMultiSimConfigChangeRegistrants() {
763         sMultiSimConfigChangeRegistrants.removeAll();
764     }
765 
broadcastMultiSimConfigChange(int numOfActiveModems)766     private void broadcastMultiSimConfigChange(int numOfActiveModems) {
767         log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems);
768         // Notify internal registrants first.
769         notifyMultiSimConfigChange(numOfActiveModems);
770 
771         Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
772         intent.putExtra(EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, numOfActiveModems);
773         mContext.sendBroadcast(intent);
774     }
775     /**
776      * This is invoked from shell commands during CTS testing only.
777      * @return true if the modem service is set successfully, false otherwise.
778      */
setModemService(String serviceName)779     public boolean setModemService(String serviceName) {
780         log("setModemService: " + serviceName);
781         boolean statusRadioConfig = false;
782         boolean statusRil = false;
783         final boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
784         final boolean isAllowedForBoot =
785                 SystemProperties.getBoolean(BOOT_ALLOW_MOCK_MODEM_PROPERTY, false);
786 
787         // Check for ALLOW_MOCK_MODEM_PROPERTY and BOOT_ALLOW_MOCK_MODEM_PROPERTY on user builds
788         if (isAllowed || isAllowedForBoot || DEBUG) {
789             if (mRadioConfig != null) {
790                 statusRadioConfig = mRadioConfig.setModemService(serviceName);
791             }
792 
793             if (!statusRadioConfig) {
794                 loge("setModemService: switching modem service for radioconfig fail");
795                 return false;
796             }
797 
798             for (int i = 0; i < getPhoneCount(); i++) {
799                 if (mPhones[i] != null) {
800                     statusRil = mPhones[i].mCi.setModemService(serviceName);
801                 }
802 
803                 if (!statusRil) {
804                     loge("setModemService: switch modem for radio " + i + " fail");
805 
806                     // Disconnect the switched service
807                     mRadioConfig.setModemService(null);
808                     for (int t = 0; t < i; t++) {
809                         mPhones[t].mCi.setModemService(null);
810                     }
811                     return false;
812                 }
813             }
814         } else {
815             loge("setModemService is not allowed");
816             return false;
817         }
818 
819         return true;
820     }
821 
822      /**
823      * This is invoked from shell commands to query during CTS testing only.
824      * @return the service name of the connected service.
825      */
getModemService()826     public String getModemService() {
827         if (mPhones[0] == null) {
828             return "";
829         }
830 
831         return mPhones[0].mCi.getModemService();
832     }
833 
834     /**
835      * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests.
836      *
837      * For example, setting or reading system property are static native methods that can't be
838      * directly mocked. We can mock it by replacing MockableInterface object with a mock instance
839      * in unittest.
840      */
841     @VisibleForTesting
842     public static class MockableInterface {
843         /**
844          * Wrapper function to decide whether reboot is required for modem config change.
845          */
846         @VisibleForTesting
isRebootRequiredForModemConfigChange()847         public boolean isRebootRequiredForModemConfigChange() {
848             boolean rebootRequired = TelephonyProperties.reboot_on_modem_change().orElse(false);
849             log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
850             return rebootRequired;
851         }
852 
853         /**
854          * Wrapper function to call setMultiSimProperties.
855          */
856         @VisibleForTesting
setMultiSimProperties(int numOfActiveModems)857         public void setMultiSimProperties(int numOfActiveModems) {
858             String multiSimConfig;
859             switch(numOfActiveModems) {
860                 case 3:
861                     multiSimConfig = TSTS;
862                     break;
863                 case 2:
864                     multiSimConfig = DSDS;
865                     break;
866                 default:
867                     multiSimConfig = SSSS;
868             }
869 
870             log("setMultiSimProperties to " + multiSimConfig);
871             TelephonyProperties.multi_sim_config(multiSimConfig);
872         }
873 
874         /**
875          * Wrapper function to call PhoneFactory.onMultiSimConfigChanged.
876          */
877         @VisibleForTesting
notifyPhoneFactoryOnMultiSimConfigChanged( Context context, int numOfActiveModems)878         public void notifyPhoneFactoryOnMultiSimConfigChanged(
879                 Context context, int numOfActiveModems) {
880             PhoneFactory.onMultiSimConfigChanged(context, numOfActiveModems);
881         }
882 
883         /**
884          * Wrapper function to query the sysprop for multi_sim_config
885          */
getMultiSimProperty()886         public Optional<String> getMultiSimProperty() {
887             return TelephonyProperties.multi_sim_config();
888         }
889     }
890 
log(String s)891     private static void log(String s) {
892         Rlog.d(LOG_TAG, s);
893     }
894 
loge(String s)895     private static void loge(String s) {
896         Rlog.e(LOG_TAG, s);
897     }
898 
loge(String s, Exception ex)899     private static void loge(String s, Exception ex) {
900         Rlog.e(LOG_TAG, s, ex);
901     }
902 }
903