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