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