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