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