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