blob: d4926b2fc3f5b5f22b6bfbbc3ee39b80b8dcece7 [file] [log] [blame]
/*
* Copyright (c) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ims;
import android.content.Context;
import android.os.IInterface;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public abstract class ImsCallbackAdapterManager<T extends IInterface> {
private static final String TAG = "ImsCallbackAM";
private final Context mContext;
private final Object mLock;
private final int mSlotId;
// Map of sub id -> List<callbacks> for sub id linked callbacks.
private final SparseArray<Set<T>> mCallbackSubscriptionMap = new SparseArray<>();
// List of all active callbacks to ImsService
private final RemoteCallbackList<T> mRemoteCallbacks = new RemoteCallbackList<>();
@VisibleForTesting
public SubscriptionManager.OnSubscriptionsChangedListener mSubChangedListener;
public ImsCallbackAdapterManager(Context context, Object lock, int slotId) {
mContext = context;
mLock = lock;
mSlotId = slotId;
if (Looper.myLooper() == null) {
Looper.prepare();
}
// Must be created after Looper.prepare() is called, or else we will get an exception.
mSubChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() {
@Override
public void onSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
if (manager == null) {
Log.w(TAG + " [" + mSlotId + "]", "onSubscriptionsChanged: could not find "
+ "SubscriptionManager.");
return;
}
List<SubscriptionInfo> subInfos = manager.getActiveSubscriptionInfoList(false);
if (subInfos == null) {
subInfos = Collections.emptyList();
}
Set<Integer> newSubIds = subInfos.stream()
.map(SubscriptionInfo::getSubscriptionId)
.collect(Collectors.toSet());
synchronized (mLock) {
Set<Integer> storedSubIds = new ArraySet<>(mCallbackSubscriptionMap.size());
for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size();
keyIndex++) {
storedSubIds.add(mCallbackSubscriptionMap.keyAt(keyIndex));
}
// Get the set of sub ids that are in storedSubIds that are not in newSubIds.
// This is the set of sub ids that need to be removed.
storedSubIds.removeAll(newSubIds);
for (Integer subId : storedSubIds) {
removeCallbacksForSubscription(subId);
}
}
}
};
}
// Add a callback to the ImsFeature associated with this manager (independent of the
// current subscription).
public final void addCallback(T localCallback) {
synchronized (mLock) {
// Skip registering to callback subscription map here, because we are registering
// for the slot, independent of subscription (deprecated behavior).
// Throws a IllegalStateException if this registration fails.
registerCallback(localCallback);
Log.i(TAG + " [" + mSlotId + "]", "Local callback added: " + localCallback);
mRemoteCallbacks.register(localCallback);
}
}
// Add a callback to be associated with a subscription. If that subscription is removed,
// remove the callback and notify the callback that the subscription has been removed.
public void addCallbackForSubscription(T localCallback, int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
Log.w(TAG + " [" + mSlotId + "]", "add callback: invalid subId " + subId);
return;
}
synchronized (mLock) {
addCallback(localCallback);
linkCallbackToSubscription(localCallback, subId);
}
}
// Removes a callback associated with the ImsFeature.
public final void removeCallback(T localCallback) {
Log.i(TAG + " [" + mSlotId + "]", "Local callback removed: " + localCallback);
synchronized (mLock) {
if (mRemoteCallbacks.unregister(localCallback)) {
// Will only occur if we have record of this callback in mRemoteCallbacks.
unregisterCallback(localCallback);
}
}
}
// Remove an existing callback that has been linked to a subscription.
public void removeCallbackForSubscription(T localCallback, int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
Log.w(TAG + " [" + mSlotId + "]", "remove callback: invalid subId " + subId);
return;
}
synchronized (mLock) {
removeCallback(localCallback);
unlinkCallbackFromSubscription(localCallback, subId);
}
}
// Links a callback to be tracked by a subscription. If it goes away, emove.
private void linkCallbackToSubscription(T callback, int subId) {
synchronized (mLock) {
if (mCallbackSubscriptionMap.size() == 0) {
// we are about to add the first entry to the map, register for subscriptions
//changed listener.
registerForSubscriptionsChanged();
}
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub == null) {
// the callback list has not been created yet for this subscription.
callbacksPerSub = new ArraySet<>();
mCallbackSubscriptionMap.put(subId, callbacksPerSub);
}
callbacksPerSub.add(callback);
}
}
// Unlink the callback from the associated subscription.
private void unlinkCallbackFromSubscription(T callback, int subId) {
synchronized (mLock) {
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub != null) {
callbacksPerSub.remove(callback);
if (callbacksPerSub.isEmpty()) {
mCallbackSubscriptionMap.remove(subId);
}
}
if (mCallbackSubscriptionMap.size() == 0) {
unregisterForSubscriptionsChanged();
}
}
}
// Removes all of the callbacks that have been registered to the subscription specified.
// This happens when Telephony sends an indication that the subscriptions have changed.
private void removeCallbacksForSubscription(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
Log.w(TAG + " [" + mSlotId + "]", "remove all callbacks: invalid subId " + subId);
return;
}
synchronized (mLock) {
Set<T> callbacksPerSub = mCallbackSubscriptionMap.get(subId);
if (callbacksPerSub == null) {
// no callbacks registered for this subscription.
return;
}
// clear all registered callbacks in the subscription map for this subscription.
mCallbackSubscriptionMap.remove(subId);
for (T callback : callbacksPerSub) {
removeCallback(callback);
}
// If there are no more callbacks being tracked, remove subscriptions changed
// listener.
if (mCallbackSubscriptionMap.size() == 0) {
unregisterForSubscriptionsChanged();
}
}
}
// Clear the Subscription -> Callback map because the ImsService connection is no longer
// current.
private void clearCallbacksForAllSubscriptions() {
synchronized (mLock) {
List<Integer> keys = new ArrayList<>();
for (int keyIndex = 0; keyIndex < mCallbackSubscriptionMap.size(); keyIndex++) {
keys.add(mCallbackSubscriptionMap.keyAt(keyIndex));
}
keys.forEach(this::removeCallbacksForSubscription);
}
}
private void registerForSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
if (manager != null) {
manager.addOnSubscriptionsChangedListener(mSubChangedListener);
} else {
Log.w(TAG + " [" + mSlotId + "]", "registerForSubscriptionsChanged: could not find"
+ " SubscriptionManager.");
}
}
private void unregisterForSubscriptionsChanged() {
SubscriptionManager manager = mContext.getSystemService(SubscriptionManager.class);
if (manager != null) {
manager.removeOnSubscriptionsChangedListener(mSubChangedListener);
} else {
Log.w(TAG + " [" + mSlotId + "]", "unregisterForSubscriptionsChanged: could not"
+ " find SubscriptionManager.");
}
}
// The ImsService these callbacks are registered to has become unavailable or crashed, or
// the ImsResolver has switched to a new ImsService. In these cases, clean up all existing
// callbacks.
public final void close() {
synchronized (mLock) {
final int lastCallbackIndex = mRemoteCallbacks.getRegisteredCallbackCount() - 1;
for(int ii = lastCallbackIndex; ii >= 0; ii --) {
T callbackItem = mRemoteCallbacks.getRegisteredCallbackItem(ii);
unregisterCallback(callbackItem);
mRemoteCallbacks.unregister(callbackItem);
}
clearCallbacksForAllSubscriptions();
Log.i(TAG + " [" + mSlotId + "]", "Closing connection and clearing callbacks");
}
}
// A callback has been registered. Register that callback with the ImsFeature.
public abstract void registerCallback(T localCallback);
// A callback has been removed, unregister that callback with the RcsFeature.
public abstract void unregisterCallback(T localCallback);
}