Abstract out common code for managing a single instance remote callback
- Pull code that manages the lifecycle of the callback into its own class which takes care of cleaning up any callback references and also listening for when the binder dies - Removing some boilerplate code Bug: 202740477 Test: atest WMShellUnitTests Change-Id: I7ff4c876fc14291d6523c2c62a7c453a01d96294
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,6 @@ import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
@@ -69,6 +68,7 @@ import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
import com.android.wm.shell.common.RemoteCallable;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SingleInstanceRemoteListener;
|
||||
import com.android.wm.shell.common.TaskStackListenerCallback;
|
||||
import com.android.wm.shell.common.TaskStackListenerImpl;
|
||||
import com.android.wm.shell.onehanded.OneHandedController;
|
||||
@@ -117,13 +117,28 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
private final Rect mTmpInsetBounds = new Rect();
|
||||
|
||||
private boolean mIsInFixedRotation;
|
||||
private IPipAnimationListener mPinnedStackAnimationRecentsCallback;
|
||||
private PipAnimationListener mPinnedStackAnimationRecentsCallback;
|
||||
|
||||
protected PhonePipMenuController mMenuController;
|
||||
protected PipTaskOrganizer mPipTaskOrganizer;
|
||||
protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener =
|
||||
new PipControllerPinnedTaskListener();
|
||||
|
||||
private interface PipAnimationListener {
|
||||
/**
|
||||
* Notifies the listener that the Pip animation is started.
|
||||
*/
|
||||
void onPipAnimationStarted();
|
||||
|
||||
/**
|
||||
* Notifies the listener about PiP round corner radius changes.
|
||||
* Listener can expect an immediate callback the first time they attach.
|
||||
*
|
||||
* @param cornerRadius the pixel value of the corner radius, zero means it's disabled.
|
||||
*/
|
||||
void onPipCornerRadiusChanged(int cornerRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for display rotation changes.
|
||||
*/
|
||||
@@ -551,7 +566,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
|
||||
}
|
||||
|
||||
private void setPinnedStackAnimationListener(IPipAnimationListener callback) {
|
||||
private void setPinnedStackAnimationListener(PipAnimationListener callback) {
|
||||
mPinnedStackAnimationRecentsCallback = callback;
|
||||
onPipCornerRadiusChanged();
|
||||
}
|
||||
@@ -560,11 +575,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
if (mPinnedStackAnimationRecentsCallback != null) {
|
||||
final int cornerRadius =
|
||||
mContext.getResources().getDimensionPixelSize(R.dimen.pip_corner_radius);
|
||||
try {
|
||||
mPinnedStackAnimationRecentsCallback.onPipCornerRadiusChanged(cornerRadius);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to call onPipCornerRadiusChanged", e);
|
||||
}
|
||||
mPinnedStackAnimationRecentsCallback.onPipCornerRadiusChanged(cornerRadius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,11 +634,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
// Disable touches while the animation is running
|
||||
mTouchHandler.setTouchEnabled(false);
|
||||
if (mPinnedStackAnimationRecentsCallback != null) {
|
||||
try {
|
||||
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to call onPinnedStackAnimationStarted()", e);
|
||||
}
|
||||
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,22 +873,25 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
@BinderThread
|
||||
private static class IPipImpl extends IPip.Stub {
|
||||
private PipController mController;
|
||||
private IPipAnimationListener mListener;
|
||||
private final IBinder.DeathRecipient mListenerDeathRecipient =
|
||||
new IBinder.DeathRecipient() {
|
||||
@Override
|
||||
@BinderThread
|
||||
public void binderDied() {
|
||||
final PipController controller = mController;
|
||||
controller.getRemoteCallExecutor().execute(() -> {
|
||||
mListener = null;
|
||||
controller.setPinnedStackAnimationListener(null);
|
||||
});
|
||||
}
|
||||
};
|
||||
private final SingleInstanceRemoteListener<PipController,
|
||||
IPipAnimationListener> mListener;
|
||||
private final PipAnimationListener mPipAnimationListener = new PipAnimationListener() {
|
||||
@Override
|
||||
public void onPipAnimationStarted() {
|
||||
mListener.call(l -> l.onPipAnimationStarted());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPipCornerRadiusChanged(int cornerRadius) {
|
||||
mListener.call(l -> l.onPipCornerRadiusChanged(cornerRadius));
|
||||
}
|
||||
};
|
||||
|
||||
IPipImpl(PipController controller) {
|
||||
mController = controller;
|
||||
mListener = new SingleInstanceRemoteListener<>(mController,
|
||||
c -> c.setPinnedStackAnimationListener(mPipAnimationListener),
|
||||
c -> c.setPinnedStackAnimationListener(null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -925,23 +935,11 @@ public class PipController implements PipTransitionController.PipTransitionCallb
|
||||
public void setPinnedStackAnimationListener(IPipAnimationListener listener) {
|
||||
executeRemoteCallWithTaskPermission(mController, "setPinnedStackAnimationListener",
|
||||
(controller) -> {
|
||||
if (mListener != null) {
|
||||
// Reset the old death recipient
|
||||
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
|
||||
0 /* flags */);
|
||||
}
|
||||
if (listener != null) {
|
||||
// Register the death recipient for the new listener to clear the listener
|
||||
try {
|
||||
listener.asBinder().linkToDeath(mListenerDeathRecipient,
|
||||
0 /* flags */);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to link to death");
|
||||
return;
|
||||
}
|
||||
mListener.register(listener);
|
||||
} else {
|
||||
mListener.unregister();
|
||||
}
|
||||
mListener = listener;
|
||||
controller.setPinnedStackAnimationListener(listener);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
@@ -61,6 +60,7 @@ import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.common.DisplayInsetsController;
|
||||
import com.android.wm.shell.common.RemoteCallable;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SingleInstanceRemoteListener;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
import com.android.wm.shell.common.annotations.ExternalThread;
|
||||
@@ -433,46 +433,26 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
||||
@BinderThread
|
||||
private static class ISplitScreenImpl extends ISplitScreen.Stub {
|
||||
private SplitScreenController mController;
|
||||
private ISplitScreenListener mListener;
|
||||
private final SingleInstanceRemoteListener<SplitScreenController,
|
||||
ISplitScreenListener> mListener;
|
||||
private final SplitScreen.SplitScreenListener mSplitScreenListener =
|
||||
new SplitScreen.SplitScreenListener() {
|
||||
@Override
|
||||
public void onStagePositionChanged(int stage, int position) {
|
||||
try {
|
||||
if (mListener != null) {
|
||||
mListener.onStagePositionChanged(stage, position);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "onStagePositionChanged", e);
|
||||
}
|
||||
mListener.call(l -> l.onStagePositionChanged(stage, position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskStageChanged(int taskId, int stage, boolean visible) {
|
||||
try {
|
||||
if (mListener != null) {
|
||||
mListener.onTaskStageChanged(taskId, stage, visible);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "onTaskStageChanged", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
private final IBinder.DeathRecipient mListenerDeathRecipient =
|
||||
new IBinder.DeathRecipient() {
|
||||
@Override
|
||||
@BinderThread
|
||||
public void binderDied() {
|
||||
final SplitScreenController controller = mController;
|
||||
controller.getRemoteCallExecutor().execute(() -> {
|
||||
mListener = null;
|
||||
controller.unregisterSplitScreenListener(mSplitScreenListener);
|
||||
});
|
||||
mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
|
||||
}
|
||||
};
|
||||
|
||||
public ISplitScreenImpl(SplitScreenController controller) {
|
||||
mController = controller;
|
||||
mListener = new SingleInstanceRemoteListener<>(controller,
|
||||
c -> c.registerSplitScreenListener(mSplitScreenListener),
|
||||
c -> c.unregisterSplitScreenListener(mSplitScreenListener));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,36 +465,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
|
||||
@Override
|
||||
public void registerSplitScreenListener(ISplitScreenListener listener) {
|
||||
executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
|
||||
(controller) -> {
|
||||
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;
|
||||
controller.registerSplitScreenListener(mSplitScreenListener);
|
||||
});
|
||||
(controller) -> mListener.register(listener));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterSplitScreenListener(ISplitScreenListener listener) {
|
||||
executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
|
||||
(controller) -> {
|
||||
if (mListener != null) {
|
||||
mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
|
||||
0 /* flags */);
|
||||
}
|
||||
mListener = null;
|
||||
controller.unregisterSplitScreenListener(mSplitScreenListener);
|
||||
});
|
||||
(controller) -> mListener.unregister());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.android.internal.util.function.TriConsumer;
|
||||
import com.android.launcher3.icons.IconProvider;
|
||||
import com.android.wm.shell.common.RemoteCallable;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SingleInstanceRemoteListener;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
|
||||
/**
|
||||
@@ -237,24 +238,19 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
@BinderThread
|
||||
private static class IStartingWindowImpl extends IStartingWindow.Stub {
|
||||
private StartingWindowController mController;
|
||||
private IStartingWindowListener mListener;
|
||||
private SingleInstanceRemoteListener<StartingWindowController,
|
||||
IStartingWindowListener> mListener;
|
||||
private final TriConsumer<Integer, Integer, Integer> mStartingWindowListener =
|
||||
this::notifyIStartingWindowListener;
|
||||
private final IBinder.DeathRecipient mListenerDeathRecipient =
|
||||
new IBinder.DeathRecipient() {
|
||||
@Override
|
||||
@BinderThread
|
||||
public void binderDied() {
|
||||
final StartingWindowController controller = mController;
|
||||
controller.getRemoteCallExecutor().execute(() -> {
|
||||
mListener = null;
|
||||
controller.setStartingWindowListener(null);
|
||||
});
|
||||
}
|
||||
(taskId, supportedType, startingWindowBackgroundColor) -> {
|
||||
mListener.call(l -> l.onTaskLaunching(taskId, supportedType,
|
||||
startingWindowBackgroundColor));
|
||||
};
|
||||
|
||||
public IStartingWindowImpl(StartingWindowController controller) {
|
||||
mController = controller;
|
||||
mListener = new SingleInstanceRemoteListener<>(controller,
|
||||
c -> c.setStartingWindowListener(mStartingWindowListener),
|
||||
c -> c.setStartingWindowListener(null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,36 +264,12 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
|
||||
public void setStartingWindowListener(IStartingWindowListener listener) {
|
||||
executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener",
|
||||
(controller) -> {
|
||||
if (mListener != null) {
|
||||
// Reset the old death recipient
|
||||
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.register(listener);
|
||||
} else {
|
||||
mListener.unregister();
|
||||
}
|
||||
mListener = listener;
|
||||
controller.setStartingWindowListener(mStartingWindowListener);
|
||||
});
|
||||
}
|
||||
|
||||
private void notifyIStartingWindowListener(int taskId, int supportedType,
|
||||
int startingWindowBackgroundColor) {
|
||||
if (mListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mListener.onTaskLaunching(taskId, supportedType, startingWindowBackgroundColor);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to notify task launching", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ public interface ShellTransitions {
|
||||
/**
|
||||
* Registers a remote transition.
|
||||
*/
|
||||
void registerRemote(@NonNull TransitionFilter filter,
|
||||
@NonNull RemoteTransition remoteTransition);
|
||||
default void registerRemote(@NonNull TransitionFilter filter,
|
||||
@NonNull RemoteTransition remoteTransition) {}
|
||||
|
||||
/**
|
||||
* Unregisters a remote transition.
|
||||
*/
|
||||
void unregisterRemote(@NonNull RemoteTransition remoteTransition);
|
||||
default void unregisterRemote(@NonNull RemoteTransition remoteTransition) {}
|
||||
}
|
||||
|
||||
@@ -171,24 +171,6 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Create an empty/non-registering transitions object for system-ui tests. */
|
||||
@VisibleForTesting
|
||||
public static ShellTransitions createEmptyForTesting() {
|
||||
return new ShellTransitions() {
|
||||
@Override
|
||||
public void registerRemote(@androidx.annotation.NonNull TransitionFilter filter,
|
||||
@androidx.annotation.NonNull RemoteTransition remoteTransition) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterRemote(
|
||||
@androidx.annotation.NonNull RemoteTransition remoteTransition) {
|
||||
// Do nothing
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Register this transition handler with Core */
|
||||
public void register(ShellTaskOrganizer taskOrganizer) {
|
||||
if (mPlayerImpl == null) return;
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.systemui.dagger.SysUIComponent;
|
||||
import com.android.systemui.dagger.WMComponent;
|
||||
import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
|
||||
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
|
||||
import com.android.wm.shell.transition.ShellTransitions;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -133,7 +134,7 @@ public class SystemUIFactory {
|
||||
.setShellCommandHandler(Optional.ofNullable(null))
|
||||
.setAppPairs(Optional.ofNullable(null))
|
||||
.setTaskViewFactory(Optional.ofNullable(null))
|
||||
.setTransitions(Transitions.createEmptyForTesting())
|
||||
.setTransitions(new ShellTransitions() {})
|
||||
.setDisplayAreaHelper(Optional.ofNullable(null))
|
||||
.setStartingSurface(Optional.ofNullable(null))
|
||||
.setTaskSurfaceHelper(Optional.ofNullable(null));
|
||||
|
||||
Reference in New Issue
Block a user