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 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Handler; 32 import android.os.HandlerExecutor; 33 import android.os.ParcelUuid; 34 import android.os.PersistableBundle; 35 import android.telephony.CarrierConfigManager; 36 import android.telephony.SubscriptionInfo; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 39 import android.telephony.TelephonyCallback; 40 import android.telephony.TelephonyManager; 41 import android.util.ArrayMap; 42 import android.util.ArraySet; 43 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.internal.annotations.VisibleForTesting.Visibility; 46 import com.android.internal.util.IndentingPrintWriter; 47 48 import java.util.Collections; 49 import java.util.HashMap; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Map.Entry; 53 import java.util.Objects; 54 import java.util.Set; 55 56 /** 57 * TelephonySubscriptionTracker provides a caching layer for tracking active subscription groups. 58 * 59 * <p>This class performs two roles: 60 * 61 * <ol> 62 * <li>De-noises subscription changes by ensuring that only changes in active and ready 63 * subscription groups are acted upon 64 * <li>Caches mapping between subIds and subscription groups 65 * </ol> 66 * 67 * <p>An subscription group is active and ready if any of its contained subIds has had BOTH the 68 * {@link CarrierConfigManager#isConfigForIdentifiedCarrier()} return true, AND the subscription is 69 * listed as active per SubscriptionManager#getAllSubscriptionInfoList(). 70 * 71 * <p>Note that due to the asynchronous nature of callbacks and broadcasts, the output of this class 72 * is (only) eventually consistent. 73 * 74 * @hide 75 */ 76 public class TelephonySubscriptionTracker extends BroadcastReceiver { 77 @NonNull private static final String TAG = TelephonySubscriptionTracker.class.getSimpleName(); 78 private static final boolean LOG_DBG = false; // STOPSHIP if true 79 80 @NonNull private final Context mContext; 81 @NonNull private final Handler mHandler; 82 @NonNull private final TelephonySubscriptionTrackerCallback mCallback; 83 @NonNull private final Dependencies mDeps; 84 85 @NonNull private final TelephonyManager mTelephonyManager; 86 @NonNull private final SubscriptionManager mSubscriptionManager; 87 @NonNull private final CarrierConfigManager mCarrierConfigManager; 88 89 @NonNull private final ActiveDataSubscriptionIdListener mActiveDataSubIdListener; 90 91 // TODO (Android T+): Add ability to handle multiple subIds per slot. 92 @NonNull private final Map<Integer, Integer> mReadySubIdsBySlotId = new HashMap<>(); 93 @NonNull private final OnSubscriptionsChangedListener mSubscriptionChangedListener; 94 95 @NonNull private TelephonySubscriptionSnapshot mCurrentSnapshot; 96 TelephonySubscriptionTracker( @onNull Context context, @NonNull Handler handler, @NonNull TelephonySubscriptionTrackerCallback callback)97 public TelephonySubscriptionTracker( 98 @NonNull Context context, 99 @NonNull Handler handler, 100 @NonNull TelephonySubscriptionTrackerCallback callback) { 101 this(context, handler, callback, new Dependencies()); 102 } 103 104 @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionTracker( @onNull Context context, @NonNull Handler handler, @NonNull TelephonySubscriptionTrackerCallback callback, @NonNull Dependencies deps)105 TelephonySubscriptionTracker( 106 @NonNull Context context, 107 @NonNull Handler handler, 108 @NonNull TelephonySubscriptionTrackerCallback callback, 109 @NonNull Dependencies deps) { 110 mContext = Objects.requireNonNull(context, "Missing context"); 111 mHandler = Objects.requireNonNull(handler, "Missing handler"); 112 mCallback = Objects.requireNonNull(callback, "Missing callback"); 113 mDeps = Objects.requireNonNull(deps, "Missing deps"); 114 115 mTelephonyManager = mContext.getSystemService(TelephonyManager.class); 116 mSubscriptionManager = mContext.getSystemService(SubscriptionManager.class); 117 mCarrierConfigManager = mContext.getSystemService(CarrierConfigManager.class); 118 mActiveDataSubIdListener = new ActiveDataSubscriptionIdListener(); 119 120 mSubscriptionChangedListener = 121 new OnSubscriptionsChangedListener() { 122 @Override 123 public void onSubscriptionsChanged() { 124 handleSubscriptionsChanged(); 125 } 126 }; 127 } 128 129 /** Registers the receivers, and starts tracking subscriptions. */ register()130 public void register() { 131 final HandlerExecutor executor = new HandlerExecutor(mHandler); 132 133 mContext.registerReceiver( 134 this, new IntentFilter(ACTION_CARRIER_CONFIG_CHANGED), null, mHandler); 135 mSubscriptionManager.addOnSubscriptionsChangedListener( 136 executor, mSubscriptionChangedListener); 137 mTelephonyManager.registerTelephonyCallback(executor, mActiveDataSubIdListener); 138 } 139 140 /** Unregisters the receivers, and stops tracking subscriptions. */ unregister()141 public void unregister() { 142 mContext.unregisterReceiver(this); 143 mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionChangedListener); 144 mTelephonyManager.unregisterTelephonyCallback(mActiveDataSubIdListener); 145 } 146 147 /** 148 * Handles subscription changes, correlating available subscriptions and loaded carrier configs 149 * 150 * <p>The subscription change listener is registered with a HandlerExecutor backed by mHandler, 151 * so callbacks & broadcasts are all serialized on mHandler, avoiding the need for locking. 152 */ handleSubscriptionsChanged()153 public void handleSubscriptionsChanged() { 154 final Map<ParcelUuid, Set<String>> privilegedPackages = new HashMap<>(); 155 final Map<Integer, SubscriptionInfo> newSubIdToInfoMap = new HashMap<>(); 156 157 final List<SubscriptionInfo> allSubs = mSubscriptionManager.getAllSubscriptionInfoList(); 158 if (allSubs == null) { 159 return; // Telephony crashed; no way to verify subscriptions. 160 } 161 162 // If allSubs is empty, no subscriptions exist. Cache will be cleared by virtue of no active 163 // subscriptions 164 for (SubscriptionInfo subInfo : allSubs) { 165 if (subInfo.getGroupUuid() == null) { 166 continue; 167 } 168 169 // Build subId -> subGrp cache 170 newSubIdToInfoMap.put(subInfo.getSubscriptionId(), subInfo); 171 172 // Update subscription groups that are both ready, and active. For a group to be 173 // considered active, both of the following must be true: 174 // 175 // 1. A final CARRIER_CONFIG_CHANGED (where config is for an identified carrier) 176 // broadcast must have been received for the subId 177 // 2. A active subscription (is loaded into a SIM slot) must be part of the subscription 178 // group. 179 if (subInfo.getSimSlotIndex() != INVALID_SIM_SLOT_INDEX 180 && mReadySubIdsBySlotId.values().contains(subInfo.getSubscriptionId())) { 181 // TODO (b/172619301): Cache based on callbacks from CarrierPrivilegesTracker 182 183 final TelephonyManager subIdSpecificTelephonyManager = 184 mTelephonyManager.createForSubscriptionId(subInfo.getSubscriptionId()); 185 186 final ParcelUuid subGroup = subInfo.getGroupUuid(); 187 final Set<String> pkgs = 188 privilegedPackages.getOrDefault(subGroup, new ArraySet<>()); 189 pkgs.addAll(subIdSpecificTelephonyManager.getPackagesWithCarrierPrivileges()); 190 191 privilegedPackages.put(subGroup, pkgs); 192 } 193 } 194 195 final TelephonySubscriptionSnapshot newSnapshot = 196 new TelephonySubscriptionSnapshot( 197 mDeps.getActiveDataSubscriptionId(), newSubIdToInfoMap, privilegedPackages); 198 199 // If snapshot was meaningfully updated, fire the callback 200 if (!newSnapshot.equals(mCurrentSnapshot)) { 201 mCurrentSnapshot = newSnapshot; 202 mHandler.post( 203 () -> { 204 mCallback.onNewSnapshot(newSnapshot); 205 }); 206 } 207 } 208 209 /** 210 * Broadcast receiver for ACTION_CARRIER_CONFIG_CHANGED 211 * 212 * <p>The broadcast receiver is registered with mHandler, so callbacks & broadcasts are all 213 * serialized on mHandler, avoiding the need for locking. 214 */ 215 @Override onReceive(Context context, Intent intent)216 public void onReceive(Context context, Intent intent) { 217 // Accept sticky broadcasts; if CARRIER_CONFIG_CHANGED was previously broadcast and it 218 // already was for an identified carrier, we can stop waiting for initial load to complete 219 if (!ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) { 220 return; 221 } 222 223 final int subId = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID); 224 final int slotId = intent.getIntExtra(EXTRA_SLOT_INDEX, INVALID_SIM_SLOT_INDEX); 225 226 if (slotId == INVALID_SIM_SLOT_INDEX) { 227 return; 228 } 229 230 if (SubscriptionManager.isValidSubscriptionId(subId)) { 231 final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId); 232 if (mDeps.isConfigForIdentifiedCarrier(carrierConfigs)) { 233 mReadySubIdsBySlotId.put(slotId, subId); 234 handleSubscriptionsChanged(); 235 } 236 } else { 237 mReadySubIdsBySlotId.remove(slotId); 238 handleSubscriptionsChanged(); 239 } 240 } 241 242 @VisibleForTesting(visibility = Visibility.PRIVATE) setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId)243 void setReadySubIdsBySlotId(Map<Integer, Integer> readySubIdsBySlotId) { 244 mReadySubIdsBySlotId.putAll(readySubIdsBySlotId); 245 } 246 247 @VisibleForTesting(visibility = Visibility.PRIVATE) getReadySubIdsBySlotId()248 Map<Integer, Integer> getReadySubIdsBySlotId() { 249 return Collections.unmodifiableMap(mReadySubIdsBySlotId); 250 } 251 252 /** TelephonySubscriptionSnapshot is a class containing info about active subscriptions */ 253 public static class TelephonySubscriptionSnapshot { 254 private final int mActiveDataSubId; 255 private final Map<Integer, SubscriptionInfo> mSubIdToInfoMap; 256 private final Map<ParcelUuid, Set<String>> mPrivilegedPackages; 257 258 public static final TelephonySubscriptionSnapshot EMPTY_SNAPSHOT = 259 new TelephonySubscriptionSnapshot( 260 INVALID_SUBSCRIPTION_ID, Collections.emptyMap(), Collections.emptyMap()); 261 262 @VisibleForTesting(visibility = Visibility.PRIVATE) TelephonySubscriptionSnapshot( int activeDataSubId, @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap, @NonNull Map<ParcelUuid, Set<String>> privilegedPackages)263 TelephonySubscriptionSnapshot( 264 int activeDataSubId, 265 @NonNull Map<Integer, SubscriptionInfo> subIdToInfoMap, 266 @NonNull Map<ParcelUuid, Set<String>> privilegedPackages) { 267 mActiveDataSubId = activeDataSubId; 268 Objects.requireNonNull(subIdToInfoMap, "subIdToInfoMap was null"); 269 Objects.requireNonNull(privilegedPackages, "privilegedPackages was null"); 270 271 mSubIdToInfoMap = Collections.unmodifiableMap(subIdToInfoMap); 272 273 final Map<ParcelUuid, Set<String>> unmodifiableInnerSets = new ArrayMap<>(); 274 for (Entry<ParcelUuid, Set<String>> entry : privilegedPackages.entrySet()) { 275 unmodifiableInnerSets.put( 276 entry.getKey(), Collections.unmodifiableSet(entry.getValue())); 277 } 278 mPrivilegedPackages = Collections.unmodifiableMap(unmodifiableInnerSets); 279 } 280 281 /** Returns the active subscription ID. May be INVALID_SUBSCRIPTION_ID */ getActiveDataSubscriptionId()282 public int getActiveDataSubscriptionId() { 283 return mActiveDataSubId; 284 } 285 286 /** Returns the active subscription group */ 287 @Nullable getActiveDataSubscriptionGroup()288 public ParcelUuid getActiveDataSubscriptionGroup() { 289 final SubscriptionInfo info = mSubIdToInfoMap.get(getActiveDataSubscriptionId()); 290 if (info == null) { 291 return null; 292 } 293 294 return info.getGroupUuid(); 295 } 296 297 /** Returns the active subscription groups */ 298 @NonNull getActiveSubscriptionGroups()299 public Set<ParcelUuid> getActiveSubscriptionGroups() { 300 return mPrivilegedPackages.keySet(); 301 } 302 303 /** Checks if the provided package is carrier privileged for the specified sub group. */ packageHasPermissionsForSubscriptionGroup( @onNull ParcelUuid subGrp, @NonNull String packageName)304 public boolean packageHasPermissionsForSubscriptionGroup( 305 @NonNull ParcelUuid subGrp, @NonNull String packageName) { 306 final Set<String> privilegedPackages = mPrivilegedPackages.get(subGrp); 307 308 return privilegedPackages != null && privilegedPackages.contains(packageName); 309 } 310 311 /** Returns the Subscription Group for a given subId. */ 312 @Nullable getGroupForSubId(int subId)313 public ParcelUuid getGroupForSubId(int subId) { 314 return mSubIdToInfoMap.containsKey(subId) 315 ? mSubIdToInfoMap.get(subId).getGroupUuid() 316 : null; 317 } 318 319 /** 320 * Returns all the subIds in a given group, including available, but inactive subscriptions. 321 */ 322 @NonNull getAllSubIdsInGroup(ParcelUuid subGrp)323 public Set<Integer> getAllSubIdsInGroup(ParcelUuid subGrp) { 324 final Set<Integer> subIds = new ArraySet<>(); 325 326 for (Entry<Integer, SubscriptionInfo> entry : mSubIdToInfoMap.entrySet()) { 327 if (subGrp.equals(entry.getValue().getGroupUuid())) { 328 subIds.add(entry.getKey()); 329 } 330 } 331 332 return subIds; 333 } 334 335 /** Checks if the requested subscription is opportunistic */ 336 @NonNull isOpportunistic(int subId)337 public boolean isOpportunistic(int subId) { 338 return mSubIdToInfoMap.containsKey(subId) 339 ? mSubIdToInfoMap.get(subId).isOpportunistic() 340 : false; 341 } 342 343 @Override hashCode()344 public int hashCode() { 345 return Objects.hash(mActiveDataSubId, mSubIdToInfoMap, mPrivilegedPackages); 346 } 347 348 @Override equals(Object obj)349 public boolean equals(Object obj) { 350 if (!(obj instanceof TelephonySubscriptionSnapshot)) { 351 return false; 352 } 353 354 final TelephonySubscriptionSnapshot other = (TelephonySubscriptionSnapshot) obj; 355 356 return mActiveDataSubId == other.mActiveDataSubId 357 && mSubIdToInfoMap.equals(other.mSubIdToInfoMap) 358 && mPrivilegedPackages.equals(other.mPrivilegedPackages); 359 } 360 361 /** Dumps the state of this snapshot for logging and debugging purposes. */ dump(IndentingPrintWriter pw)362 public void dump(IndentingPrintWriter pw) { 363 pw.println("TelephonySubscriptionSnapshot:"); 364 pw.increaseIndent(); 365 366 pw.println("mActiveDataSubId: " + mActiveDataSubId); 367 pw.println("mSubIdToInfoMap: " + mSubIdToInfoMap); 368 pw.println("mPrivilegedPackages: " + mPrivilegedPackages); 369 370 pw.decreaseIndent(); 371 } 372 373 @Override toString()374 public String toString() { 375 return "TelephonySubscriptionSnapshot{ " 376 + "mActiveDataSubId=" + mActiveDataSubId 377 + ", mSubIdToInfoMap=" + mSubIdToInfoMap 378 + ", mPrivilegedPackages=" + mPrivilegedPackages 379 + " }"; 380 } 381 } 382 383 /** 384 * Interface for listening to changes in subscriptions 385 * 386 * @see TelephonySubscriptionTracker 387 */ 388 public interface TelephonySubscriptionTrackerCallback { 389 /** 390 * Called when subscription information changes, and a new subscription snapshot was taken 391 * 392 * @param snapshot the snapshot of subscription information. 393 */ onNewSnapshot(@onNull TelephonySubscriptionSnapshot snapshot)394 void onNewSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot); 395 } 396 397 private class ActiveDataSubscriptionIdListener extends TelephonyCallback 398 implements TelephonyCallback.ActiveDataSubscriptionIdListener { 399 @Override onActiveDataSubscriptionIdChanged(int subId)400 public void onActiveDataSubscriptionIdChanged(int subId) { 401 handleSubscriptionsChanged(); 402 } 403 } 404 405 /** External static dependencies for test injection */ 406 @VisibleForTesting(visibility = Visibility.PRIVATE) 407 public static class Dependencies { 408 /** Checks if the given bundle is for an identified carrier */ isConfigForIdentifiedCarrier(PersistableBundle bundle)409 public boolean isConfigForIdentifiedCarrier(PersistableBundle bundle) { 410 return CarrierConfigManager.isConfigForIdentifiedCarrier(bundle); 411 } 412 413 /** Gets the active Subscription ID */ getActiveDataSubscriptionId()414 public int getActiveDataSubscriptionId() { 415 return SubscriptionManager.getActiveDataSubscriptionId(); 416 } 417 } 418 } 419