Merge "Add BinderCacheManager: caches Binder & notifies listeners upon death"
This commit is contained in:
197
telephony/java/android/telephony/BinderCacheManager.java
Normal file
197
telephony/java/android/telephony/BinderCacheManager.java
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 android.telephony;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Keeps track of the connection to a Binder node, refreshes the cache if the node dies, and lets
|
||||
* interested parties register listeners on the node to be notified when the node has died via the
|
||||
* registered {@link Runnable}.
|
||||
* @param <T> The IInterface representing the Binder type that this manager will be managing the
|
||||
* cache of.
|
||||
* @hide
|
||||
*/
|
||||
public class BinderCacheManager<T extends IInterface> {
|
||||
|
||||
/**
|
||||
* Factory class for creating new IInterfaces in the case that {@link #getBinder()} is
|
||||
* called and there is no active binder available.
|
||||
* @param <T> The IInterface that should be cached and returned to the caller when
|
||||
* {@link #getBinder()} is called until the Binder node dies.
|
||||
*/
|
||||
public interface BinderInterfaceFactory<T> {
|
||||
/**
|
||||
* @return A new instance of the Binder node, which will be cached until it dies.
|
||||
*/
|
||||
T create();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the cached Binder node as well as the listeners that were associated with that
|
||||
* Binder node during its lifetime. If the Binder node dies, the listeners will be called and
|
||||
* then this tracker will be unlinked and cleaned up.
|
||||
*/
|
||||
private class BinderDeathTracker implements IBinder.DeathRecipient {
|
||||
|
||||
private final T mConnection;
|
||||
private final HashMap<Object, Runnable> mListeners = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Create a tracker to cache the Binder node and add the ability to listen for the cached
|
||||
* interface's death.
|
||||
*/
|
||||
BinderDeathTracker(@NonNull T connection) {
|
||||
mConnection = connection;
|
||||
try {
|
||||
mConnection.asBinder().linkToDeath(this, 0 /*flags*/);
|
||||
} catch (RemoteException e) {
|
||||
// isAlive will return false.
|
||||
}
|
||||
}
|
||||
|
||||
public boolean addListener(Object key, Runnable r) {
|
||||
synchronized (mListeners) {
|
||||
if (!isAlive()) return false;
|
||||
mListeners.put(key, r);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeListener(Object runnableKey) {
|
||||
synchronized (mListeners) {
|
||||
mListeners.remove(runnableKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void binderDied() {
|
||||
ArrayList<Runnable> listeners;
|
||||
synchronized (mListeners) {
|
||||
listeners = new ArrayList<>(mListeners.values());
|
||||
mListeners.clear();
|
||||
try {
|
||||
mConnection.asBinder().unlinkToDeath(this, 0 /*flags*/);
|
||||
} catch (NoSuchElementException e) {
|
||||
// No need to worry about this, this means the death recipient was never linked.
|
||||
}
|
||||
}
|
||||
listeners.forEach(Runnable::run);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The cached Binder.
|
||||
*/
|
||||
public T getConnection() {
|
||||
return mConnection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the cached Binder is alive at the time of calling, false otherwise.
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return mConnection.asBinder().isBinderAlive();
|
||||
}
|
||||
}
|
||||
|
||||
private final BinderInterfaceFactory<T> mBinderInterfaceFactory;
|
||||
private final AtomicReference<BinderDeathTracker> mCachedConnection;
|
||||
|
||||
/**
|
||||
* Create a new instance, which manages a cached IInterface and creates new ones using the
|
||||
* provided factory when the cached IInterface dies.
|
||||
* @param factory The factory used to create new Instances of the cached IInterface when it
|
||||
* dies.
|
||||
*/
|
||||
public BinderCacheManager(BinderInterfaceFactory<T> factory) {
|
||||
mBinderInterfaceFactory = factory;
|
||||
mCachedConnection = new AtomicReference<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the binder node connection and add a Runnable to be run if this Binder dies. Once this
|
||||
* Runnable is run, the Runnable itself is discarded and must be added again.
|
||||
* <p>
|
||||
* Note: There should be no assumptions here as to which Thread this Runnable is called on. If
|
||||
* the Runnable should be called on a specific thread, it should be up to the caller to handle
|
||||
* that in the runnable implementation.
|
||||
* @param runnableKey The Key associated with this runnable so that it can be removed later
|
||||
* using {@link #removeRunnable(Object)} if needed.
|
||||
* @param deadRunnable The runnable that will be run if the cached Binder node dies.
|
||||
* @return T if the runnable was added or {@code null} if the connection is not alive right now
|
||||
* and the associated runnable was never added.
|
||||
*/
|
||||
public T listenOnBinder(Object runnableKey, Runnable deadRunnable) {
|
||||
if (runnableKey == null || deadRunnable == null) return null;
|
||||
BinderDeathTracker tracker = getTracker();
|
||||
if (tracker == null) return null;
|
||||
|
||||
boolean addSucceeded = tracker.addListener(runnableKey, deadRunnable);
|
||||
return addSucceeded ? tracker.getConnection() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The cached Binder node. May return null if the requested Binder node is not currently
|
||||
* available.
|
||||
*/
|
||||
public T getBinder() {
|
||||
BinderDeathTracker tracker = getTracker();
|
||||
return (tracker != null) ? tracker.getConnection() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously registered runnable associated with the returned cached Binder node
|
||||
* using the key it was registered with in {@link #listenOnBinder} if the runnable still exists.
|
||||
* @param runnableKey The key that was used to register the Runnable earlier.
|
||||
* @return The cached Binder node that the runnable used to registered to or null if the cached
|
||||
* Binder node is not alive anymore.
|
||||
*/
|
||||
public T removeRunnable(Object runnableKey) {
|
||||
if (runnableKey == null) return null;
|
||||
BinderDeathTracker tracker = getTracker();
|
||||
if (tracker == null) return null;
|
||||
tracker.removeListener(runnableKey);
|
||||
return tracker.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The BinderDeathTracker container, which contains the cached IInterface instance or
|
||||
* null if it is not available right now.
|
||||
*/
|
||||
private BinderDeathTracker getTracker() {
|
||||
return mCachedConnection.updateAndGet((oldVal) -> {
|
||||
BinderDeathTracker tracker = oldVal;
|
||||
// Update cache if no longer alive. BinderDied will eventually be called on the tracker,
|
||||
// which will call listeners & clean up.
|
||||
if (tracker == null || !tracker.isAlive()) {
|
||||
T binder = mBinderInterfaceFactory.create();
|
||||
tracker = (binder != null) ? new BinderDeathTracker(binder) : null;
|
||||
|
||||
}
|
||||
return (tracker != null && tracker.isAlive()) ? tracker : null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user