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 android.telephony; 18 19 import android.annotation.NonNull; 20 import android.os.IBinder; 21 import android.os.IInterface; 22 import android.os.RemoteException; 23 24 import java.util.ArrayList; 25 import java.util.HashMap; 26 import java.util.NoSuchElementException; 27 import java.util.concurrent.atomic.AtomicReference; 28 29 /** 30 * Keeps track of the connection to a Binder node, refreshes the cache if the node dies, and lets 31 * interested parties register listeners on the node to be notified when the node has died via the 32 * registered {@link Runnable}. 33 * @param <T> The IInterface representing the Binder type that this manager will be managing the 34 * cache of. 35 * @hide 36 */ 37 public class BinderCacheManager<T extends IInterface> { 38 39 /** 40 * Factory class for creating new IInterfaces in the case that {@link #getBinder()} is 41 * called and there is no active binder available. 42 * @param <T> The IInterface that should be cached and returned to the caller when 43 * {@link #getBinder()} is called until the Binder node dies. 44 */ 45 public interface BinderInterfaceFactory<T> { 46 /** 47 * @return A new instance of the Binder node, which will be cached until it dies. 48 */ create()49 T create(); 50 } 51 52 /** 53 * Tracks the cached Binder node as well as the listeners that were associated with that 54 * Binder node during its lifetime. If the Binder node dies, the listeners will be called and 55 * then this tracker will be unlinked and cleaned up. 56 */ 57 private class BinderDeathTracker implements IBinder.DeathRecipient { 58 59 private final T mConnection; 60 private final HashMap<Object, Runnable> mListeners = new HashMap<>(); 61 62 /** 63 * Create a tracker to cache the Binder node and add the ability to listen for the cached 64 * interface's death. 65 */ BinderDeathTracker(@onNull T connection)66 BinderDeathTracker(@NonNull T connection) { 67 mConnection = connection; 68 try { 69 mConnection.asBinder().linkToDeath(this, 0 /*flags*/); 70 } catch (RemoteException e) { 71 // isAlive will return false. 72 } 73 } 74 addListener(Object key, Runnable r)75 public boolean addListener(Object key, Runnable r) { 76 synchronized (mListeners) { 77 if (!isAlive()) return false; 78 mListeners.put(key, r); 79 return true; 80 } 81 } 82 removeListener(Object runnableKey)83 public void removeListener(Object runnableKey) { 84 synchronized (mListeners) { 85 mListeners.remove(runnableKey); 86 } 87 } 88 89 @Override binderDied()90 public void binderDied() { 91 ArrayList<Runnable> listeners; 92 synchronized (mListeners) { 93 listeners = new ArrayList<>(mListeners.values()); 94 mListeners.clear(); 95 try { 96 mConnection.asBinder().unlinkToDeath(this, 0 /*flags*/); 97 } catch (NoSuchElementException e) { 98 // No need to worry about this, this means the death recipient was never linked. 99 } 100 } 101 listeners.forEach(Runnable::run); 102 } 103 104 /** 105 * @return The cached Binder. 106 */ getConnection()107 public T getConnection() { 108 return mConnection; 109 } 110 111 /** 112 * @return true if the cached Binder is alive at the time of calling, false otherwise. 113 */ isAlive()114 public boolean isAlive() { 115 return mConnection.asBinder().isBinderAlive(); 116 } 117 } 118 119 private final BinderInterfaceFactory<T> mBinderInterfaceFactory; 120 private final AtomicReference<BinderDeathTracker> mCachedConnection; 121 122 /** 123 * Create a new instance, which manages a cached IInterface and creates new ones using the 124 * provided factory when the cached IInterface dies. 125 * @param factory The factory used to create new Instances of the cached IInterface when it 126 * dies. 127 */ BinderCacheManager(BinderInterfaceFactory<T> factory)128 public BinderCacheManager(BinderInterfaceFactory<T> factory) { 129 mBinderInterfaceFactory = factory; 130 mCachedConnection = new AtomicReference<>(); 131 } 132 133 /** 134 * Get the binder node connection and add a Runnable to be run if this Binder dies. Once this 135 * Runnable is run, the Runnable itself is discarded and must be added again. 136 * <p> 137 * Note: There should be no assumptions here as to which Thread this Runnable is called on. If 138 * the Runnable should be called on a specific thread, it should be up to the caller to handle 139 * that in the runnable implementation. 140 * @param runnableKey The Key associated with this runnable so that it can be removed later 141 * using {@link #removeRunnable(Object)} if needed. 142 * @param deadRunnable The runnable that will be run if the cached Binder node dies. 143 * @return T if the runnable was added or {@code null} if the connection is not alive right now 144 * and the associated runnable was never added. 145 */ listenOnBinder(Object runnableKey, Runnable deadRunnable)146 public T listenOnBinder(Object runnableKey, Runnable deadRunnable) { 147 if (runnableKey == null || deadRunnable == null) return null; 148 BinderDeathTracker tracker = getTracker(); 149 if (tracker == null) return null; 150 151 boolean addSucceeded = tracker.addListener(runnableKey, deadRunnable); 152 return addSucceeded ? tracker.getConnection() : null; 153 } 154 155 /** 156 * @return The cached Binder node. May return null if the requested Binder node is not currently 157 * available. 158 */ getBinder()159 public T getBinder() { 160 BinderDeathTracker tracker = getTracker(); 161 return (tracker != null) ? tracker.getConnection() : null; 162 } 163 164 /** 165 * Removes a previously registered runnable associated with the returned cached Binder node 166 * using the key it was registered with in {@link #listenOnBinder} if the runnable still exists. 167 * @param runnableKey The key that was used to register the Runnable earlier. 168 * @return The cached Binder node that the runnable used to registered to or null if the cached 169 * Binder node is not alive anymore. 170 */ removeRunnable(Object runnableKey)171 public T removeRunnable(Object runnableKey) { 172 if (runnableKey == null) return null; 173 BinderDeathTracker tracker = getTracker(); 174 if (tracker == null) return null; 175 tracker.removeListener(runnableKey); 176 return tracker.getConnection(); 177 } 178 179 /** 180 * @return The BinderDeathTracker container, which contains the cached IInterface instance or 181 * null if it is not available right now. 182 */ getTracker()183 private BinderDeathTracker getTracker() { 184 return mCachedConnection.updateAndGet((oldVal) -> { 185 BinderDeathTracker tracker = oldVal; 186 // Update cache if no longer alive. BinderDied will eventually be called on the tracker, 187 // which will call listeners & clean up. 188 if (tracker == null || !tracker.isAlive()) { 189 T binder = mBinderInterfaceFactory.create(); 190 tracker = (binder != null) ? new BinderDeathTracker(binder) : null; 191 192 } 193 return (tracker != null && tracker.isAlive()) ? tracker : null; 194 }); 195 } 196 197 } 198