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