blob: 0c93f7f160e4149520805c44096fdd594f2bc798 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
/**
* Multiplexes multiple binder death recipients on the same binder objects, so that at the native
* level, we only need to keep track of one death recipient reference. This will help reduce the
* number of JNI strong references.
*
* test with: atest FrameworksCoreTests:BinderDeathDispatcherTest
*/
public class BinderDeathDispatcher<T extends IInterface> {
private static final String TAG = "BinderDeathDispatcher";
private final Object mLock = new Object();
@GuardedBy("mLock")
private final ArrayMap<IBinder, RecipientsInfo> mTargets = new ArrayMap<>();
@VisibleForTesting
class RecipientsInfo implements DeathRecipient {
final IBinder mTarget;
/**
* Recipient list. If it's null, {@link #mTarget} has already died, but in that case
* this RecipientsInfo instance is removed from {@link #mTargets}.
*/
@GuardedBy("mLock")
@Nullable
ArraySet<DeathRecipient> mRecipients = new ArraySet<>();
private RecipientsInfo(IBinder target) {
mTarget = target;
}
@Override
public void binderDied() {
final ArraySet<DeathRecipient> copy;
synchronized (mLock) {
copy = mRecipients;
mRecipients = null;
// Also remove from the targets.
mTargets.remove(mTarget);
}
if (copy == null) {
return;
}
// Let's call it without holding the lock.
final int size = copy.size();
for (int i = 0; i < size; i++) {
copy.valueAt(i).binderDied();
}
}
}
/**
* Add a {@code recipient} to the death recipient list on {@code target}.
*
* @return # of recipients in the recipient list, including {@code recipient}. Or, -1
* if {@code target} is already dead, in which case recipient's
* {@link DeathRecipient#binderDied} won't be called.
*/
public int linkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
final IBinder ib = target.asBinder();
synchronized (mLock) {
RecipientsInfo info = mTargets.get(ib);
if (info == null) {
info = new RecipientsInfo(ib);
// First recipient; need to link to death.
try {
ib.linkToDeath(info, 0);
} catch (RemoteException e) {
return -1; // Already dead.
}
mTargets.put(ib, info);
}
info.mRecipients.add(recipient);
return info.mRecipients.size();
}
}
public void unlinkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) {
final IBinder ib = target.asBinder();
synchronized (mLock) {
final RecipientsInfo info = mTargets.get(ib);
if (info == null) {
return;
}
if (info.mRecipients.remove(recipient) && info.mRecipients.size() == 0) {
info.mTarget.unlinkToDeath(info, 0);
mTargets.remove(info.mTarget);
}
}
}
public void dump(PrintWriter pw, String indent) {
synchronized (mLock) {
pw.print(indent);
pw.print("# of watched binders: ");
pw.println(mTargets.size());
pw.print(indent);
pw.print("# of death recipients: ");
int n = 0;
for (RecipientsInfo info : mTargets.values()) {
n += info.mRecipients.size();
}
pw.println(n);
}
}
@VisibleForTesting
public ArrayMap<IBinder, RecipientsInfo> getTargetsForTest() {
return mTargets;
}
}