1 /*
2  * Copyright (C) 2020 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.server.vcn;
18 
19 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
20 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
21 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.vcn.VcnManager;
30 import android.os.Handler;
31 import android.os.HandlerExecutor;
32 import android.os.ParcelUuid;
33 import android.os.PersistableBundle;
34 import android.telephony.CarrierConfigManager;
35 import android.telephony.SubscriptionInfo;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
38 import android.telephony.TelephonyCallback;
39 import android.telephony.TelephonyManager;
40 import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
41 import android.util.ArrayMap;
42 import android.util.ArraySet;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.annotations.VisibleForTesting.Visibility;
47 import com.android.internal.util.IndentingPrintWriter;
48 import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.Iterator;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Map.Entry;
57 import java.util.Objects;
58 import java.util.Set;
59 
60 /**
61  * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups.
62  *
63  * <p>This class performs two roles:
64  *
65  * <ol>
66  *   <li>De-noises subscription changes by ensuring that only changes in active and ready
67  *       subscription groups are acted upon
68  *   <li>Caches mapping between subIds and subscription groups
69  * </ol>
70  *
71  * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the
72  * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is
73  * listed as active per SubscriptionManager#getAllSubscriptionInfoList().
74  *
75  * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class
76  * is (only) eventually consistent.
77  *
78  * @hide
79  */
80 public class TelephonySubscriptionTracker extends BroadcastReceiver {
81     @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName();
82     private static final boolean LOG_DBG = false; // STOPSHIP if true
83 
84     @NonNull private final Context mContext;
85     @NonNull private final Handler mHandler;
86     @NonNull private final TelephonySubscriptionTrackerCallback mCallback;
87     @NonNull private final Dependencies mDeps;
88 
89     @NonNull private final TelephonyManager mTelephonyManager;
90     @NonNull private final SubscriptionManager mSubscriptionManager;
91     @NonNull private final CarrierConfigManager mCarrierConfigManager;
92 
93     @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener;
94 
95     // TODO (Android T+): Add ability to handle multiple subIds per slot.
96     @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>();
97 
98     @NonNull
99     private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap = new HashMap<>();
100 
101     @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener;
102 
103     @NonNull
104     private final List<CarrierPrivilegesCallback> mCarrierPrivilegesCallbacks = new ArrayList<>();
105 
106     @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot;
107 
108     @NonNull
109     private final CarrierConfigManager.CarrierConfigChangeListener mCarrierConfigChangeListener =
110             (int logicalSlotIndex, int subscriptionId, int carrierId, int specificCarrierId) ->
111                     handleActionCarrierConfigChanged(logicalSlotIndex, subscriptionId);
112 
113 
TelephonySubscriptionTracker( @onNull Context context, @NonNull Handler handler, @NonNull TelephonySubscriptionTrackerCallback callback)114     public TelephonySubscriptionTracker(
115             @NonNull Context context,
116             @NonNull Handler handler,
117             @NonNull TelephonySubscriptionTrackerCallback callback) {
118         this(context, handler, callback, new Dependencies());
119     }
120 
121     @VisibleForTesting(visibility = Visibility.PRIVATE)
TelephonySubscriptionTracker( @onNull Context context, @NonNull Handler handler, @NonNull TelephonySubscriptionTrackerCallback callback, @NonNull Dependencies deps)122     TelephonySubscriptionTracker(
123             @NonNull Context context,
124             @NonNull Handler handler,
125             @NonNull TelephonySubscriptionTrackerCallback callback,
126             @NonNull Dependencies deps) {
127         mContext = Objects.requireNonNull(context, "Missing context");
128         mHandler = Objects.requireNonNull(handler, "Missing handler");
129         mCallback = Objects.requireNonNull(callback, "Missing callback");
130         mDeps = Objects.requireNonNull(deps, "Missing deps");
131 
132         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
133         mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class);
134         mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class);
135         mActiveDataSubIdListener = new ActiveDataSubscriptionIdListener();
136 
137         mSubscriptionChangedListener =
138                 new OnSubscriptionsChangedListener() {
139                     @Override
140                     public void onSubscriptionsChanged() {
141                         handleSubscriptionsChanged();
142                     }
143                 };
144     }
145 
146     /**
147      * Registers the receivers, and starts tracking subscriptions.
148      *
149      * <p>Must always be run on the VcnManagementService thread.
150      */
register()151     public void register() {
152         final HandlerExecutor executor = new HandlerExecutor(mHandler);
153         final IntentFilter filter = new IntentFilter();
154         filter.addAction(ACTION_MULTI_SIM_CONFIG_CHANGED);
155 
156         mContext.registerReceiver(this, filter, null, mHandler);
157         mSubscriptionManager.addOnSubscriptionsChangedListener(
158                 executor, mSubscriptionChangedListener);
159         mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener);
160         mCarrierConfigManager.registerCarrierConfigChangeListener(executor,
161                 mCarrierConfigChangeListener);
162 
163         registerCarrierPrivilegesCallbacks();
164     }
165 
166     // TODO(b/221306368): Refactor with the new onCarrierServiceChange in the new CPCallback
registerCarrierPrivilegesCallbacks()167     private void registerCarrierPrivilegesCallbacks() {
168         final HandlerExecutor executor = new HandlerExecutor(mHandler);
169         final int modemCount = mTelephonyManager.getActiveModemCount();
170         try {
171             for (int i = 0; i < modemCount; i++) {
172                 CarrierPrivilegesCallback carrierPrivilegesCallback =
173                         new CarrierPrivilegesCallback() {
174                             @Override
175                             public void onCarrierPrivilegesChanged(
176                                     @NonNull Set<String> privilegedPackageNames,
177                                     @NonNull Set<Integer> privilegedUids) {
178                                 // Re-trigger the synchronous check (which is also very cheap due
179                                 // to caching in CarrierPrivilegesTracker). This allows consistency
180                                 // with the onSubscriptionsChangedListener and broadcasts.
181                                 handleSubscriptionsChanged();
182                             }
183                         };
184 
185                 mTelephonyManager.registerCarrierPrivilegesCallback(
186                         i, executor, carrierPrivilegesCallback);
187                 mCarrierPrivilegesCallbacks.add(carrierPrivilegesCallback);
188             }
189         } catch (IllegalArgumentException e) {
190             Slog.wtf(TAG, "Encounted exception registering carrier privileges listeners", e);
191         }
192     }
193 
194     /**
195      * Unregisters the receivers, and stops tracking subscriptions.
196      *
197      * <p>Must always be run on the VcnManagementService thread.
198      */
unregister()199     public void unregister() {
200         mContext.unregisterReceiver(this);
201         mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener);
202         mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener);
203         mCarrierConfigManager.unregisterCarrierConfigChangeListener(mCarrierConfigChangeListener);
204 
205         unregisterCarrierPrivilegesCallbacks();
206     }
207 
unregisterCarrierPrivilegesCallbacks()208     private void unregisterCarrierPrivilegesCallbacks() {
209         for (CarrierPrivilegesCallback carrierPrivilegesCallback :
210                 mCarrierPrivilegesCallbacks) {
211             mTelephonyManager.unregisterCarrierPrivilegesCallback(carrierPrivilegesCallback);
212         }
213         mCarrierPrivilegesCallbacks.clear();
214     }
215 
216     /**
217      * Handles subscription changes, correlating available subscriptions and loaded carrier configs
218      *
219      * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler,
220      * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking.
221      */
handleSubscriptionsChanged()222     public void handleSubscriptionsChanged() {
223         final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>();
224         final Map<Integer, SubscriptionInfo> newSubIdToInfoMap = new HashMap<>();
225 
226         final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList();
227         if (allSubs == null) {
228             return; // Telephony crashed; no way to verify subscriptions.
229         }
230 
231         // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active
232         // subscriptions
233         for (SubscriptionInfo subInfo : allSubs) {
234             if (subInfo.getGroupUuid() == null) {
235                 continue;
236             }
237 
238             // Build subId -> subGrp cache
239             newSubIdToInfoMap.put(subInfo.getSubscriptionId(), subInfo);
240 
241             // Update subscription groups that are both ready, and active. For a group to be
242             // considered active, both of the following must be true:
243             //
244             // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier)
245             // broadcast must have been received for the subId
246             // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription
247             // group.
248             if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX
249                     && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) {
250                 final TelephonyManager subIdSpecificTelephonyManager =
251                         mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId());
252 
253                 final ParcelUuid subGroup = subInfo.getGroupUuid();
254                 final Set<String> pkgs =
255                         privilegedPackages.getOrDefault(subGroup, new ArraySet<>());
256                 pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges());
257 
258                 privilegedPackages.put(subGroup, pkgs);
259             }
260         }
261 
262         final TelephonySubscriptionSnapshot newSnapshot =
263                 new TelephonySubscriptionSnapshot(
264                         mDeps.getActiveDataSubscriptionId(),
265                         newSubIdToInfoMap,
266                         mSubIdToCarrierConfigMap,
267                         privilegedPackages);
268 
269         // If snapshot was meaningfully updated, fire the callback
270         if (!newSnapshot.equals(mCurrentSnapshot)) {
271             mCurrentSnapshot = newSnapshot;
272             mHandler.post(
273                     () -> {
274                         mCallback.onNewSnapshot(newSnapshot);
275                     });
276         }
277     }
278 
279     /**
280      * Broadcast receiver for ACTION_MULTI_SIM_CONFIG_CHANGED
281      *
282      * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all
283      * serialized on mHandler, avoiding the need for locking.
284      */
285     @Override
onReceive(Context context, Intent intent)286     public void onReceive(Context context, Intent intent) {
287         switch (intent.getAction()) {
288             case ACTION_MULTI_SIM_CONFIG_CHANGED:
289                 handleActionMultiSimConfigChanged(context, intent);
290                 break;
291             default:
292                 Slog.v(TAG, "Unknown intent received with action: " + intent.getAction());
293         }
294     }
295 
handleActionMultiSimConfigChanged(Context context, Intent intent)296     private void handleActionMultiSimConfigChanged(Context context, Intent intent) {
297         unregisterCarrierPrivilegesCallbacks();
298 
299         // Clear invalid slotIds from the mReadySubIdsBySlotId map.
300         final int modemCount = mTelephonyManager.getActiveModemCount();
301         final Iterator<Integer> slotIdIterator = mReadySubIdsBySlotId.keySet().iterator();
302         while (slotIdIterator.hasNext()) {
303             final int slotId = slotIdIterator.next();
304 
305             if (slotId >= modemCount) {
306                 slotIdIterator.remove();
307             }
308         }
309 
310         registerCarrierPrivilegesCallbacks();
311         handleSubscriptionsChanged();
312     }
313 
handleActionCarrierConfigChanged(int slotId, int subId)314     private void handleActionCarrierConfigChanged(int slotId, int subId) {
315         if (slotId == INVALID_SIM_SLOT_INDEX) {
316             return;
317         }
318 
319         if (SubscriptionManager.isValidSubscriptionId(subId)) {
320             // Get only configs as needed to save memory.
321             final PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(subId,
322                     VcnManager.VCN_RELATED_CARRIER_CONFIG_KEYS);
323             if (mDeps.isConfigForIdentifiedCarrier(carrierConfig)) {
324                 mReadySubIdsBySlotId.put(slotId, subId);
325 
326                 if (!carrierConfig.isEmpty()) {
327                     mSubIdToCarrierConfigMap.put(subId,
328                             new PersistableBundleWrapper(carrierConfig));
329                 }
330                 handleSubscriptionsChanged();
331             }
332         } else {
333             final Integer oldSubid = mReadySubIdsBySlotId.remove(slotId);
334             if (oldSubid != null) {
335                 mSubIdToCarrierConfigMap.remove(oldSubid);
336             }
337             handleSubscriptionsChanged();
338         }
339     }
340 
341     @VisibleForTesting(visibility = Visibility.PRIVATE)
setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId)342     void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) {
343         mReadySubIdsBySlotId.clear();
344         mReadySubIdsBySlotId.putAll(readySubIdsBySlotId);
345     }
346 
347     @VisibleForTesting(visibility = Visibility.PRIVATE)
setSubIdToCarrierConfigMap( Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap)348     void setSubIdToCarrierConfigMap(
349             Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap) {
350         mSubIdToCarrierConfigMap.clear();
351         mSubIdToCarrierConfigMap.putAll(subIdToCarrierConfigMap);
352     }
353 
354     @VisibleForTesting(visibility = Visibility.PRIVATE)
getReadySubIdsBySlotId()355     Map<Integer, Integer> getReadySubIdsBySlotId() {
356         return Collections.unmodifiableMap(mReadySubIdsBySlotId);
357     }
358 
359     @VisibleForTesting(visibility = Visibility.PRIVATE)
getSubIdToCarrierConfigMap()360     Map<Integer, PersistableBundleWrapper> getSubIdToCarrierConfigMap() {
361         return Collections.unmodifiableMap(mSubIdToCarrierConfigMap);
362     }
363 
364     /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */
365     public static class TelephonySubscriptionSnapshot {
366         private final int mActiveDataSubId;
367         private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap;
368         private final Map<Integer, PersistableBundleWrapper> mSubIdToCarrierConfigMap;
369         private final Map<ParcelUuid, Set<String>> mPrivilegedPackages;
370 
371         public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT =
372                 new TelephonySubscriptionSnapshot(
373                         INVALID_SUBSCRIPTION_ID,
374                         Collections.emptyMap(),
375                         Collections.emptyMap(),
376                         Collections.emptyMap());
377 
378         @VisibleForTesting(visibility = Visibility.PRIVATE)
TelephonySubscriptionSnapshot( int activeDataSubId, @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap, @NonNull Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap, @NonNull Map<ParcelUuid, Set<String>> privilegedPackages)379         TelephonySubscriptionSnapshot(
380                 int activeDataSubId,
381                 @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap,
382                 @NonNull Map<Integer, PersistableBundleWrapper> subIdToCarrierConfigMap,
383                 @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) {
384             mActiveDataSubId = activeDataSubId;
385             Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null");
386             Objects.requireNonNull(privilegedPackages, "privilegedPackages was null");
387             Objects.requireNonNull(subIdToCarrierConfigMap, "subIdToCarrierConfigMap was null");
388 
389             mSubIdToInfoMap =
390                     Collections.unmodifiableMap(
391                             new HashMap<Integer, SubscriptionInfo>(subIdToInfoMap));
392             mSubIdToCarrierConfigMap =
393                     Collections.unmodifiableMap(
394                             new HashMap<Integer, PersistableBundleWrapper>(
395                                     subIdToCarrierConfigMap));
396 
397             final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>();
398             for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) {
399                 unmodifiableInnerSets.put(
400                         entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
401             }
402             mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets);
403         }
404 
405         /** Returns the active subscription ID. May be INVALID_SUBSCRIPTION_ID */
getActiveDataSubscriptionId()406         public int getActiveDataSubscriptionId() {
407             return mActiveDataSubId;
408         }
409 
410         /** Returns the active subscription group */
411         @Nullable
getActiveDataSubscriptionGroup()412         public ParcelUuid getActiveDataSubscriptionGroup() {
413             final SubscriptionInfo info = mSubIdToInfoMap.get(getActiveDataSubscriptionId());
414             if (info == null) {
415                 return null;
416             }
417 
418             return info.getGroupUuid();
419         }
420 
421         /** Returns the active subscription groups */
422         @NonNull
getActiveSubscriptionGroups()423         public Set<ParcelUuid> getActiveSubscriptionGroups() {
424             return mPrivilegedPackages.keySet();
425         }
426 
427         /** Checks if the provided package is carrier privileged for the specified sub group. */
packageHasPermissionsForSubscriptionGroup( @onNull ParcelUuid subGrp, @NonNull String packageName)428         public boolean packageHasPermissionsForSubscriptionGroup(
429                 @NonNull ParcelUuid subGrp, @NonNull String packageName) {
430             final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp);
431 
432             return privilegedPackages != null && privilegedPackages.contains(packageName);
433         }
434 
435         /** Returns the Subscription Group for a given subId. */
436         @Nullable
getGroupForSubId(int subId)437         public ParcelUuid getGroupForSubId(int subId) {
438             return mSubIdToInfoMap.containsKey(subId)
439                     ? mSubIdToInfoMap.get(subId).getGroupUuid()
440                     : null;
441         }
442 
443         /**
444          * Returns all the subIds in a given group, including available, but inactive subscriptions.
445          */
446         @NonNull
getAllSubIdsInGroup(ParcelUuid subGrp)447         public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) {
448             final Set<Integer> subIds = new ArraySet<>();
449 
450             for (Entry<Integer, SubscriptionInfo> entry : mSubIdToInfoMap.entrySet()) {
451                 if (subGrp.equals(entry.getValue().getGroupUuid())) {
452                     subIds.add(entry.getKey());
453                 }
454             }
455 
456             return subIds;
457         }
458 
459         /** Checks if the requested subscription is opportunistic */
460         @NonNull
isOpportunistic(int subId)461         public boolean isOpportunistic(int subId) {
462             return mSubIdToInfoMap.containsKey(subId)
463                     ? mSubIdToInfoMap.get(subId).isOpportunistic()
464                     : false;
465         }
466 
467         /**
468          * Retrieves a carrier config for a subscription in the provided group.
469          *
470          * <p>This method will prioritize non-opportunistic subscriptions, but will use the a
471          * carrier config for an opportunistic subscription if no other subscriptions are found.
472          */
473         @Nullable
getCarrierConfigForSubGrp(@onNull ParcelUuid subGrp)474         public PersistableBundleWrapper getCarrierConfigForSubGrp(@NonNull ParcelUuid subGrp) {
475             PersistableBundleWrapper result = null;
476 
477             for (int subId : getAllSubIdsInGroup(subGrp)) {
478                 final PersistableBundleWrapper config = mSubIdToCarrierConfigMap.get(subId);
479                 if (config != null) {
480                     result = config;
481 
482                     // Attempt to use (any) non-opportunistic subscription. If this subscription is
483                     // opportunistic, continue and try to find a non-opportunistic subscription,
484                     // using the opportunistic ones as a last resort.
485                     if (!isOpportunistic(subId)) {
486                         return config;
487                     }
488                 }
489             }
490 
491             return result;
492         }
493 
494         @Override
hashCode()495         public int hashCode() {
496             return Objects.hash(
497                     mActiveDataSubId,
498                     mSubIdToInfoMap,
499                     mSubIdToCarrierConfigMap,
500                     mPrivilegedPackages);
501         }
502 
503         @Override
equals(Object obj)504         public boolean equals(Object obj) {
505             if (!(obj instanceof TelephonySubscriptionSnapshot)) {
506                 return false;
507             }
508 
509             final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj;
510 
511             return mActiveDataSubId == other.mActiveDataSubId
512                     && mSubIdToInfoMap.equals(other.mSubIdToInfoMap)
513                     && mSubIdToCarrierConfigMap.equals(other.mSubIdToCarrierConfigMap)
514                     && mPrivilegedPackages.equals(other.mPrivilegedPackages);
515         }
516 
517         /** Dumps the state of this snapshot for logging and debugging purposes. */
dump(IndentingPrintWriter pw)518         public void dump(IndentingPrintWriter pw) {
519             pw.println("TelephonySubscriptionSnapshot:");
520             pw.increaseIndent();
521 
522             pw.println("mActiveDataSubId: " + mActiveDataSubId);
523             pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap);
524             pw.println("mSubIdToCarrierConfigMap: " + mSubIdToCarrierConfigMap);
525             pw.println("mPrivilegedPackages: " + mPrivilegedPackages);
526 
527             pw.decreaseIndent();
528         }
529 
530         @Override
toString()531         public String toString() {
532             return "TelephonySubscriptionSnapshot{ "
533                     + "mActiveDataSubId=" + mActiveDataSubId
534                     + ", mSubIdToInfoMap=" + mSubIdToInfoMap
535                     + ", mSubIdToCarrierConfigMap=" + mSubIdToCarrierConfigMap
536                     + ", mPrivilegedPackages=" + mPrivilegedPackages
537                     + " }";
538         }
539     }
540 
541     /**
542      * Interface for listening to changes in subscriptions
543      *
544      * @see TelephonySubscriptionTracker
545      */
546     public interface TelephonySubscriptionTrackerCallback {
547         /**
548          * Called when subscription information changes, and a new subscription snapshot was taken
549          *
550          * @param snapshot the snapshot of subscription information.
551          */
onNewSnapshot(@onNull TelephonySubscriptionSnapshot snapshot)552         void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot);
553     }
554 
555     private class ActiveDataSubscriptionIdListener extends TelephonyCallback
556             implements TelephonyCallback.ActiveDataSubscriptionIdListener {
557         @Override
onActiveDataSubscriptionIdChanged(int subId)558         public void onActiveDataSubscriptionIdChanged(int subId) {
559             handleSubscriptionsChanged();
560         }
561     }
562 
563     /** External static dependencies for test injection */
564     @VisibleForTesting(visibility = Visibility.PRIVATE)
565     public static class Dependencies {
566         /** Checks if the given bundle is for an identified carrier */
isConfigForIdentifiedCarrier(PersistableBundle bundle)567         public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) {
568             return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle);
569         }
570 
571         /** Gets the active Subscription ID */
getActiveDataSubscriptionId()572         public int getActiveDataSubscriptionId() {
573             return SubscriptionManager.getActiveDataSubscriptionId();
574         }
575     }
576 }
577