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