blob: b77ac8a2b951e1f2b75562e23f54e89970c8f4e6 [file] [log] [blame]
/*
* Copyright (C) 2021 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.wm.shell.common;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.util.Slog;
import androidx.annotation.BinderThread;
import java.util.function.Consumer;
/**
* Manages the lifecycle of a single instance of a remote listener, including the clean up if the
* remote process dies. All calls on this class should happen on the main shell thread.
*
* @param <C> The controller (must be RemoteCallable)
* @param <L> The remote listener interface type
*/
public class SingleInstanceRemoteListener<C extends RemoteCallable, L extends IInterface> {
private static final String TAG = SingleInstanceRemoteListener.class.getSimpleName();
/**
* Simple callable interface that throws a remote exception.
*/
public interface RemoteCall<L> {
void accept(L l) throws RemoteException;
}
private final C mCallableController;
private final Consumer<C> mOnRegisterCallback;
private final Consumer<C> mOnUnregisterCallback;
L mListener;
private final IBinder.DeathRecipient mListenerDeathRecipient =
new IBinder.DeathRecipient() {
@Override
@BinderThread
public void binderDied() {
final C callableController = mCallableController;
mCallableController.getRemoteCallExecutor().execute(() -> {
mListener = null;
mOnUnregisterCallback.accept(callableController);
});
}
};
/**
* @param onRegisterCallback Callback when register() is called (same thread)
* @param onUnregisterCallback Callback when unregister() is called (same thread as unregister()
* or the callableController.getRemoteCallbackExecutor() thread)
*/
public SingleInstanceRemoteListener(C callableController,
Consumer<C> onRegisterCallback,
Consumer<C> onUnregisterCallback) {
mCallableController = callableController;
mOnRegisterCallback = onRegisterCallback;
mOnUnregisterCallback = onUnregisterCallback;
}
/**
* Registers this listener, storing a reference to it and calls the provided method in the
* constructor.
*/
public void register(L listener) {
if (mListener != null) {
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, 0 /* flags */);
}
if (listener != null) {
try {
listener.asBinder().linkToDeath(mListenerDeathRecipient, 0 /* flags */);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to link to death");
return;
}
}
mListener = listener;
mOnRegisterCallback.accept(mCallableController);
}
/**
* Unregisters this listener, removing all references to it and calls the provided method in the
* constructor.
*/
public void unregister() {
if (mListener != null) {
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient, 0 /* flags */);
}
mListener = null;
mOnUnregisterCallback.accept(mCallableController);
}
/**
* Safely wraps a call to the remote listener.
*/
public void call(RemoteCall<L> handler) {
if (mListener == null) {
Slog.e(TAG, "Failed remote call on null listener");
return;
}
try {
handler.accept(mListener);
} catch (RemoteException e) {
Slog.e(TAG, "Failed remote call", e);
}
}
}