1 /*
2  * Copyright (C) 2018 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.server.wifi.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Handler;
22 import android.os.IBinder;
23 import android.os.RemoteException;
24 import android.util.Log;
25 
26 import com.android.internal.util.Preconditions;
27 
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 
33 /**
34  * Holds a list of external app-provided binder callback objects and tracks the death
35  * of the callback object.
36  * @param <T> Callback object type.
37  */
38 public class ExternalCallbackTracker<T> {
39     private static final String TAG = "WifiExternalCallbackTracker";
40 
41     /* Limit on number of registered callbacks to track and prevent potential memory leak */
42     private static final int NUM_CALLBACKS_WARN_LIMIT = 10;
43     private static final int NUM_CALLBACKS_WTF_LIMIT = 20;
44 
45     /**
46      * Container for storing info about each external callback and tracks it's death.
47      */
48     private static class ExternalCallbackHolder<T> implements IBinder.DeathRecipient {
49         private final IBinder mBinder;
50         private final T mCallbackObject;
51         private final DeathCallback mDeathCallback;
52 
53         /**
54          * Callback to be invoked on death of the app hosting the binder.
55          */
56         public interface DeathCallback {
57             /**
58              * Called when the corresponding app has died.
59              */
onDeath()60             void onDeath();
61         }
62 
ExternalCallbackHolder(@onNull IBinder binder, @NonNull T callbackObject, @NonNull DeathCallback deathCallback)63         private ExternalCallbackHolder(@NonNull IBinder binder, @NonNull T callbackObject,
64                                        @NonNull DeathCallback deathCallback) {
65             mBinder = Preconditions.checkNotNull(binder);
66             mCallbackObject = Preconditions.checkNotNull(callbackObject);
67             mDeathCallback = Preconditions.checkNotNull(deathCallback);
68         }
69 
70         /**
71          * Static method to create a new {@link ExternalCallbackHolder} object and register for
72          * death notification of the associated binder.
73          * @return an instance of {@link ExternalCallbackHolder} if there are no failures, otherwise
74          * null.
75          */
createAndLinkToDeath( @onNull IBinder binder, @NonNull T callbackObject, @NonNull DeathCallback deathCallback)76         public static <T> ExternalCallbackHolder<T> createAndLinkToDeath(
77                 @NonNull IBinder binder, @NonNull T callbackObject,
78                 @NonNull DeathCallback deathCallback) {
79             ExternalCallbackHolder<T> externalCallback =
80                     new ExternalCallbackHolder<>(binder, callbackObject, deathCallback);
81             try {
82                 binder.linkToDeath(externalCallback, 0);
83             } catch (RemoteException e) {
84                 Log.e(TAG, "Error on linkToDeath - " + e);
85                 return null;
86             }
87             return externalCallback;
88         }
89 
90         /**
91          * Unlinks this object from binder death.
92          */
reset()93         public void reset() {
94             mBinder.unlinkToDeath(this, 0);
95         }
96 
97         /**
98          * Retrieve the callback object.
99          */
getCallback()100         public T getCallback() {
101             return mCallbackObject;
102         }
103 
104         /**
105          * App hosting the binder has died.
106          */
107         @Override
binderDied()108         public void binderDied() {
109             mDeathCallback.onDeath();
110             Log.d(TAG, "Binder died " + mBinder);
111         }
112     }
113 
114     private final Map<Integer, ExternalCallbackHolder<T>> mCallbacks;
115     private final Handler mHandler;
116 
ExternalCallbackTracker(Handler handler)117     public ExternalCallbackTracker(Handler handler) {
118         mHandler = handler;
119         mCallbacks = new HashMap<>();
120     }
121 
122     /**
123      * Add a callback object to tracker.
124      * @return true on success, false on failure.
125      */
add(@onNull IBinder binder, @NonNull T callbackObject, int callbackIdentifier)126     public boolean add(@NonNull IBinder binder, @NonNull T callbackObject, int callbackIdentifier) {
127         ExternalCallbackHolder<T> externalCallback = ExternalCallbackHolder.createAndLinkToDeath(
128                 binder, callbackObject, () -> {
129                     mHandler.post(() -> {
130                         Log.d(TAG, "Remove external callback on death " + callbackIdentifier);
131                         remove(callbackIdentifier);
132                     });
133                 });
134         if (externalCallback == null) return false;
135         if (mCallbacks.containsKey(callbackIdentifier)) {
136             Log.d(TAG, "Replacing callback " + callbackIdentifier);
137             remove(callbackIdentifier);
138         }
139         mCallbacks.put(callbackIdentifier, externalCallback);
140         if (mCallbacks.size() > NUM_CALLBACKS_WTF_LIMIT) {
141             Log.wtf(TAG, "Too many callbacks: " + mCallbacks.size());
142         } else if (mCallbacks.size() > NUM_CALLBACKS_WARN_LIMIT) {
143             Log.w(TAG, "Too many callbacks: " + mCallbacks.size());
144         }
145         return true;
146     }
147 
148     /**
149      * Remove a callback object to tracker.
150      * @return Removed object instance on success, null on failure.
151      */
remove(int callbackIdentifier)152     public @Nullable T remove(int callbackIdentifier) {
153         ExternalCallbackHolder<T> externalCallback = mCallbacks.remove(callbackIdentifier);
154         if (externalCallback == null) {
155             Log.w(TAG, "Unknown external callback " + callbackIdentifier);
156             return null;
157         }
158         externalCallback.reset();
159         return externalCallback.getCallback();
160     }
161 
162     /**
163      * Retrieve all the callback objects in the tracker.
164      */
getCallbacks()165     public List<T> getCallbacks() {
166         List<T> callbacks = new ArrayList<>();
167         for (ExternalCallbackHolder<T> externalCallback : mCallbacks.values()) {
168             callbacks.add(externalCallback.getCallback());
169         }
170         return callbacks;
171     }
172 
173     /**
174      * Retrieve the number of callback objects in the tracker.
175      */
getNumCallbacks()176     public int getNumCallbacks() {
177         return mCallbacks.size();
178     }
179 
180     /**
181      * Remove all callbacks registered.
182      */
clear()183     public void clear() {
184         for (ExternalCallbackHolder<T> externalCallback : mCallbacks.values()) {
185             externalCallback.reset();
186         }
187         mCallbacks.clear();
188     }
189 }
190