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