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