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