• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.vcn;
18 
19 import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
20 import static android.telephony.CarrierConfigManager.EXTRA_SLOT_INDEX;
21 import static android.telephony.CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX;
22 import static android.telephony.SubscriptionManager.INVALID_SIM_SLOT_INDEX;
23 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
24 
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