• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2019 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.ims;
18 
19 import android.content.Context;
20 import android.os.IInterface;
21 import android.os.Looper;
22 import android.os.RemoteCallbackList;
23 import android.telephony.SubscriptionInfo;
24 import android.telephony.SubscriptionManager;
25 import android.util.ArraySet;
26 import android.util.Log;
27 import android.util.SparseArray;
28 
29 import com.android.internal.annotations.VisibleForTesting;
30 
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Set;
35 import java.util.stream.Collectors;
36 
37 public abstract class ImsCallbackAdapterManager<T extends IInterface> {
38     private static final String TAG = "ImsCallbackAM";
39 
40     private final Context mContext;
41     private final Object mLock;
42     private final int mSlotId;
43 
44     // Map of sub id -> List<callbacks> for sub id linked callbacks.
45     private final SparseArray<Set<T>> mCallbackSubscriptionMap = new SparseArray<>();
46 
47     // List of all active callbacks to ImsService
48     private final RemoteCallbackList<T> mRemoteCallbacks = new RemoteCallbackList<>();
49 
50     @VisibleForTesting
51     public SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
52 
ImsCallbackAdapterManager(Context context, Object lock, int slotId)53     public ImsCallbackAdapterManager(Context context, Object lock, int slotId) {
54         mContext = context;
55         mLock = lock;
56         mSlotId = slotId;
57 
58         if (Looper.myLooper() == null) {
59             Looper.prepare();
60         }
61 
62         // Must be created after Looper.prepare() is called, or else we will get an exception.
63         mSubChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() {
64             @Override
65             public void onSubscriptionsChanged() {
66                 SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
67                 if (manager == null) {
68                     Log.w(TAG + " [" + mSlotId + "]", "onSubscriptionsChanged: could not find "
69                             + "SubscriptionManager.");
70                     return;
71                 }
72 
73                 List<SubscriptionInfo> subInfos = manager.getActiveSubscriptionInfoList(false);
74                 if (subInfos == null) {
75                     subInfos = Collections.emptyList();
76                 }
77 
78                 Set<Integer> newSubIds = subInfos.stream()
79                         .map(SubscriptionInfo::getSubscriptionId)
80                         .collect(Collectors.toSet());
81 
82                 synchronized (mLock) {
83                     Set<Integer> storedSubIds = new ArraySet<>(mCallbackSubscriptionMap.size());
84                     for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size();
85                             keyIndex++) {
86                         storedSubIds.add(mCallbackSubscriptionMap.keyAt(keyIndex));
87                     }
88 
89                     // Get the set of sub ids that are in storedSubIds that are not in newSubIds.
90                     // This is the set of sub ids that need to be removed.
91                     storedSubIds.removeAll(newSubIds);
92 
93                     for (Integer subId : storedSubIds) {
94                         removeCallbacksForSubscription(subId);
95                     }
96                 }
97             }
98         };
99     }
100 
101     // Add a callback to the ImsFeature associated with this manager (independent of the
102     // current subscription).
addCallback(T localCallback)103     public final void addCallback(T localCallback) {
104         synchronized (mLock) {
105             // Skip registering to callback subscription map here, because we are registering
106             // for the slot, independent of subscription (deprecated behavior).
107             // Throws a IllegalStateException if this registration fails.
108             registerCallback(localCallback);
109             Log.i(TAG + " [" + mSlotId + "]", "Local callback added: " + localCallback);
110             mRemoteCallbacks.register(localCallback);
111         }
112     }
113 
114     // Add a callback to be associated with a subscription. If that subscription is removed,
115     // remove the callback and notify the callback that the subscription has been removed.
addCallbackForSubscription(T localCallback, int subId)116     public void addCallbackForSubscription(T localCallback, int subId) {
117         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
118             Log.w(TAG + " [" + mSlotId + "]", "add callback: invalid subId " + subId);
119             return;
120         }
121         synchronized (mLock) {
122             addCallback(localCallback);
123             linkCallbackToSubscription(localCallback, subId);
124         }
125     }
126 
127     // Removes a callback associated with the ImsFeature.
removeCallback(T localCallback)128     public final void removeCallback(T localCallback) {
129         Log.i(TAG + " [" + mSlotId + "]", "Local callback removed: " + localCallback);
130         synchronized (mLock) {
131             if (mRemoteCallbacks.unregister(localCallback)) {
132                 // Will only occur if we have record of this callback in mRemoteCallbacks.
133                 unregisterCallback(localCallback);
134             }
135         }
136     }
137 
138     // Remove an existing callback that has been linked to a subscription.
removeCallbackForSubscription(T localCallback, int subId)139     public void removeCallbackForSubscription(T localCallback, int subId) {
140         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
141             Log.w(TAG + " [" + mSlotId + "]", "remove callback: invalid subId " + subId);
142             return;
143         }
144         synchronized (mLock) {
145             removeCallback(localCallback);
146             unlinkCallbackFromSubscription(localCallback, subId);
147         }
148     }
149 
150     // Links a callback to be tracked by a subscription. If it goes away, emove.
linkCallbackToSubscription(T callback, int subId)151     private void linkCallbackToSubscription(T callback, int subId) {
152         synchronized (mLock) {
153             if (mCallbackSubscriptionMap.size() == 0) {
154                 // we are about to add the first entry to the map, register for subscriptions
155                 //changed listener.
156                 registerForSubscriptionsChanged();
157             }
158             Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
159             if (callbacksPerSub == null) {
160                 // the callback list has not been created yet for this subscription.
161                 callbacksPerSub = new ArraySet<>();
162                 mCallbackSubscriptionMap.put(subId, callbacksPerSub);
163             }
164             callbacksPerSub.add(callback);
165         }
166     }
167 
168     // Unlink the callback from the associated subscription.
unlinkCallbackFromSubscription(T callback, int subId)169     private void unlinkCallbackFromSubscription(T callback, int subId) {
170         synchronized (mLock) {
171             Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
172             if (callbacksPerSub != null) {
173                 callbacksPerSub.remove(callback);
174                 if (callbacksPerSub.isEmpty()) {
175                     mCallbackSubscriptionMap.remove(subId);
176                 }
177             }
178             if (mCallbackSubscriptionMap.size() == 0) {
179                 unregisterForSubscriptionsChanged();
180             }
181         }
182     }
183 
184     // Removes all of the callbacks that have been registered to the subscription specified.
185     // This happens when Telephony sends an indication that the subscriptions have changed.
removeCallbacksForSubscription(int subId)186     private void removeCallbacksForSubscription(int subId) {
187         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
188             Log.w(TAG + " [" + mSlotId + "]", "remove all callbacks: invalid subId " + subId);
189             return;
190         }
191         synchronized (mLock) {
192             Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
193             if (callbacksPerSub == null) {
194                 // no callbacks registered for this subscription.
195                 return;
196             }
197             // clear all registered callbacks in the subscription map for this subscription.
198             mCallbackSubscriptionMap.remove(subId);
199             for (T callback : callbacksPerSub) {
200                 removeCallback(callback);
201             }
202             // If there are no more callbacks being tracked, remove subscriptions changed
203             // listener.
204             if (mCallbackSubscriptionMap.size() == 0) {
205                 unregisterForSubscriptionsChanged();
206             }
207         }
208     }
209 
210     // Clear the Subscription -> Callback map because the ImsService connection is no longer
211     // current.
clearCallbacksForAllSubscriptions()212     private void clearCallbacksForAllSubscriptions() {
213         synchronized (mLock) {
214             List<Integer> keys = new ArrayList<>();
215             for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size(); keyIndex++) {
216                 keys.add(mCallbackSubscriptionMap.keyAt(keyIndex));
217             }
218             keys.forEach(this::removeCallbacksForSubscription);
219         }
220     }
221 
registerForSubscriptionsChanged()222     private void registerForSubscriptionsChanged() {
223         SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
224         if (manager != null) {
225             manager.addOnSubscriptionsChangedListener(mSubChangedListener);
226         } else {
227             Log.w(TAG + " [" + mSlotId + "]", "registerForSubscriptionsChanged: could not find"
228                     + " SubscriptionManager.");
229         }
230     }
231 
unregisterForSubscriptionsChanged()232     private void unregisterForSubscriptionsChanged() {
233         SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
234         if (manager != null) {
235         manager.removeOnSubscriptionsChangedListener(mSubChangedListener);
236         } else {
237             Log.w(TAG + " [" + mSlotId + "]", "unregisterForSubscriptionsChanged: could not"
238                     + " find SubscriptionManager.");
239         }
240     }
241 
242     // The ImsService these callbacks are registered to has become unavailable or crashed, or
243     // the ImsResolver has switched to a new ImsService. In these cases, clean up all existing
244     // callbacks.
close()245     public final void close() {
246         synchronized (mLock) {
247             final int lastCallbackIndex = mRemoteCallbacks.getRegisteredCallbackCount() - 1;
248             for(int ii = lastCallbackIndex; ii >= 0; ii --) {
249                 T callbackItem = mRemoteCallbacks.getRegisteredCallbackItem(ii);
250                 unregisterCallback(callbackItem);
251                 mRemoteCallbacks.unregister(callbackItem);
252             }
253             clearCallbacksForAllSubscriptions();
254             Log.i(TAG + " [" + mSlotId + "]", "Closing connection and clearing callbacks");
255         }
256     }
257 
258     // A callback has been registered. Register that callback with the ImsFeature.
registerCallback(T localCallback)259     public abstract void registerCallback(T localCallback);
260 
261     // A callback has been removed, unregister that callback with the RcsFeature.
unregisterCallback(T localCallback)262     public abstract void unregisterCallback(T localCallback);
263 }
264