1 /*
2  * Copyright (C) 2023 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 package com.android.car.internal.util;
17 
18 import android.annotation.Nullable;
19 import android.car.builtin.util.Slogf;
20 import android.os.IBinder;
21 import android.os.IInterface;
22 import android.os.RemoteException;
23 import android.util.ArrayMap;
24 
25 import com.android.internal.annotations.GuardedBy;
26 
27 import java.util.Collections;
28 import java.util.Objects;
29 import java.util.Set;
30 
31 /**
32  * A map-like container to hold client's binder interface. The item in the container will be removed
33  * automatically once the associated binder is unlinked (dies).
34  *
35  * @param <K> type of the key of the item
36  * @param <V> type wrapped in the value of the item
37  *
38  * @hide
39  */
40 public final class BinderKeyValueContainer<K, V extends IInterface> {
41 
42     private static final String TAG = BinderKeyValueContainer.class.getSimpleName();
43 
44     // BinderInterfaceHolder#binderDied() is called on binder thread, and it might change this
45     // container, so guard this container with a lock to avoid racing condition between binder
46     // thread and the calling thread of this container.
47     private final Object mLock = new Object();
48 
49     @GuardedBy("mLock")
50     private final ArrayMap<K, BinderInterfaceHolder<K, V>> mBinderMap;
51 
52     @Nullable
53     private BinderDeathCallback<K> mBinderDeathCallback;
54 
55     /**
56      * Wrapper class for objects that want to be notified whenever they are unlinked from
57      * the container ({@link BinderKeyValueContainer}).
58      *
59      * @param <K> type of the key of the item
60      * @param <V> type of the value wrapped by this class
61      */
62     private static final class BinderInterfaceHolder<K, V extends IInterface> implements
63             IBinder.DeathRecipient {
64 
65         private final V mBinderInterface;
66         private final IBinder mBinder;
67         private final BinderKeyValueContainer<K, V> mMap;
68 
BinderInterfaceHolder(BinderKeyValueContainer<K, V> map, V binderInterface, IBinder binder)69         private BinderInterfaceHolder(BinderKeyValueContainer<K, V> map, V binderInterface,
70                 IBinder binder) {
71             mMap = map;
72             this.mBinderInterface = binderInterface;
73             this.mBinder = binder;
74         }
75 
76         @Override
binderDied()77         public void binderDied() {
78             mBinder.unlinkToDeath(this, 0);
79             mMap.removeByBinderInterfaceHolder(this);
80         }
81     }
82 
83     /**
84      * Interface to be implemented by object that wants to be notified whenever a binder is unlinked
85      * (dies).
86      *
87      * @param <K> type of the key of the container
88      */
89     public interface BinderDeathCallback<K> {
90         /** Callback to be invoked after a binder is unlinked and removed from the container. */
onBinderDied(K deadKey)91         void onBinderDied(K deadKey);
92     }
93 
BinderKeyValueContainer()94     public BinderKeyValueContainer() {
95         mBinderMap = new ArrayMap<>();
96     }
97 
98     /**
99      * Returns the {@link IInterface} object associated with the {@code key}, or {@code null} if
100      * there is no such key.
101      */
102     @Nullable
get(K key)103     public V get(K key) {
104         Objects.requireNonNull(key);
105         synchronized (mLock) {
106             BinderInterfaceHolder<K, V> holder = mBinderMap.get(key);
107             return holder == null ? null : holder.mBinderInterface;
108         }
109     }
110 
111     /**
112      * Adds the instance of {@link IInterface} representing the binder interface to this container.
113      * <p>
114      * Updates the value if the {@code key} exists already.
115      * <p>
116      * Internally, this {@code binderInterface} will be wrapped in a {@link BinderInterfaceHolder}
117      * when added.
118      */
put(K key, V binderInterface)119     public void put(K key, V binderInterface) {
120         IBinder binder = binderInterface.asBinder();
121         BinderInterfaceHolder<K, V> holder =
122                 new BinderInterfaceHolder<>(this, binderInterface, binder);
123         BinderInterfaceHolder<K, V> oldHolder;
124         try {
125             binder.linkToDeath(holder, 0);
126         } catch (RemoteException e) {
127             throw new IllegalArgumentException(e);
128         }
129         synchronized (mLock) {
130             oldHolder = mBinderMap.put(key, holder);
131         }
132         if (oldHolder != null) {
133             Slogf.i(TAG, "Replaced the existing callback %s", oldHolder.mBinderInterface);
134         }
135     }
136 
137     /**
138      * Removes an item in the container by its key, if there is any.
139      */
remove(K key)140     public void remove(K key) {
141         synchronized (mLock) {
142             BinderInterfaceHolder<K, V> holder = mBinderMap.get(key);
143             if (holder == null) {
144                 Slogf.i(TAG, "Failed to remove because there was no item with key %s", key);
145                 return;
146             }
147             holder.mBinder.unlinkToDeath(holder, 0);
148             mBinderMap.remove(key);
149         }
150     }
151 
152     /**
153      * Removes the item at the given index, if there is any.
154      */
removeAt(int index)155     public void removeAt(int index) {
156         synchronized (mLock) {
157             BinderInterfaceHolder<K, V> holder = mBinderMap.valueAt(index);
158             if (holder == null) {
159                 Slogf.i(TAG, "Failed to remove because there was no item at index %d", index);
160                 return;
161             }
162             holder.mBinder.unlinkToDeath(holder, 0);
163             mBinderMap.removeAt(index);
164         }
165     }
166 
167     /**
168      * Returns the number of registered {@link BinderInterfaceHolder} objects in this container.
169      */
size()170     public int size() {
171         synchronized (mLock) {
172             return mBinderMap.size();
173         }
174     }
175 
176     /**
177      * Returns the key at the given index in the container.
178      *
179      * @param index The desired index, must be between 0 and {@link #size()}-1.
180      * @throws ArrayIndexOutOfBoundsException if the index is invalid
181      */
keyAt(int index)182     public K keyAt(int index) {
183         synchronized (mLock) {
184             return mBinderMap.keyAt(index);
185         }
186     }
187 
188     /**
189      * Returns the {@link IInterface} at the given index in the container.
190      *
191      * @param index The desired index, must be between 0 and {@link #size()}-1.
192      * @throws ArrayIndexOutOfBoundsException if the index is invalid
193      */
valueAt(int index)194     public V valueAt(int index) {
195         synchronized (mLock) {
196             BinderInterfaceHolder<K, V> holder = mBinderMap.valueAt(index);
197             return holder.mBinderInterface;
198         }
199     }
200 
201     /**
202      * Returns whether the {@code key} is stored in the container.
203      */
containsKey(K key)204     public boolean containsKey(K key) {
205         synchronized (mLock) {
206             return mBinderMap.containsKey(key);
207         }
208     }
209 
210     /**
211      * Returns an unmodifiable copy of keys in the container, or an empty set if the container is
212      * empty.
213      */
keySet()214     public Set<K> keySet() {
215         synchronized (mLock) {
216             return Collections.unmodifiableSet(mBinderMap.keySet());
217         }
218     }
219 
220     /**
221      * Sets a death callback to be notified after a binder is unlinked and removed from the
222      * container.
223      */
setBinderDeathCallback(@ullable BinderDeathCallback<K> binderDeathCallback)224     public void setBinderDeathCallback(@Nullable BinderDeathCallback<K> binderDeathCallback) {
225         mBinderDeathCallback = binderDeathCallback;
226     }
227 
removeByBinderInterfaceHolder(BinderInterfaceHolder<K, V> holder)228     private void removeByBinderInterfaceHolder(BinderInterfaceHolder<K, V> holder) {
229         K deadKey = null;
230         synchronized (mLock) {
231             int index = mBinderMap.indexOfValue(holder);
232             if (index >= 0) {
233                 deadKey = mBinderMap.keyAt(index);
234                 mBinderMap.removeAt(index);
235                 Slogf.i(TAG, "Binder died, so remove %s", holder.mBinderInterface);
236             }
237         }
238         if (mBinderDeathCallback != null && deadKey != null) {
239             mBinderDeathCallback.onBinderDied(deadKey);
240         }
241     }
242 }
243