6/ Update pip to use shell main thread

- Remove some currently unused pip interface methods
- Remove some usages of handler
- Remove update thread (will eventually run on shell main thread)

Bug: 161979899
Test: atest WMShellUnitTests
Change-Id: If97e5182e396029120f0d0fb9e730ece93cddcc8
Signed-off-by: Winson Chung <winsonc@google.com>
This commit is contained in:
Winson Chung
2021-01-11 21:22:24 -08:00
parent 77bb0b7291
commit d19e69d0bc
27 changed files with 603 additions and 676 deletions

View File

@@ -472,11 +472,6 @@ class PhysicsAnimator<T> private constructor (target: T) {
* animator is under test.
*/
internal fun startInternal() {
if (!Looper.getMainLooper().isCurrentThread) {
Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
"this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
"your test setup.")
}
val target = weakTarget.get()
if (target == null) {
Log.w(TAG, "Trying to animate a GC-ed object.")

View File

@@ -17,10 +17,15 @@
package com.android.wm.shell.common;
import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Super basic Executor interface that adds support for delayed execution and removing callbacks.
@@ -65,17 +70,17 @@ public interface ShellExecutor extends Executor {
/**
* See {@link android.os.Handler#postDelayed(Runnable, long)}.
*/
void executeDelayed(Runnable r, long delayMillis);
void executeDelayed(Runnable runnable, long delayMillis);
/**
* See {@link android.os.Handler#removeCallbacks}.
*/
void removeCallbacks(Runnable r);
void removeCallbacks(Runnable runnable);
/**
* See {@link android.os.Handler#hasCallbacks(Runnable)}.
*/
boolean hasCallback(Runnable r);
boolean hasCallback(Runnable runnable);
/**
* Returns the looper that this executor is running on.

View File

@@ -34,54 +34,17 @@ import java.util.function.Consumer;
*/
@ExternalThread
public interface Pip {
/**
* Closes PIP (PIPed activity and PIP system UI).
*/
default void closePip() {
}
/**
* Dump the current state and information if need.
*
* @param pw The stream to dump information to.
*/
default void dump(PrintWriter pw) {
}
/**
* Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/
default void expandPip() {
}
/**
* Get the touch handler which manages all the touch handling for PIP on the Phone,
* including moving, dismissing and expanding the PIP. (Do not use in TV)
*
* @return
*/
default @Nullable PipTouchHandler getPipTouchHandler() {
return null;
}
/**
* Hides the PIP menu.
*/
default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {}
/**
* Returns {@code true} if PIP is shown.
*/
default boolean isPipShown() {
return false;
}
/**
* Moves the PIPed activity to the fullscreen and closes PIP system UI.
*/
default void movePipToFullscreen() {
}
/**
* Called when configuration is changed.
*/
@@ -100,12 +63,6 @@ public interface Pip {
default void onOverlayChanged() {
}
/**
* Registers the session listener for the current user.
*/
default void registerSessionListenerForCurrentUser() {
}
/**
* Called when SysUI state changed.
*
@@ -116,19 +73,9 @@ public interface Pip {
}
/**
* Resize the Pip to the appropriate size for the input state.
*
* @param state In Pip state also used to determine the new size for the Pip.
* Registers the session listener for the current user.
*/
default void resizePinnedStack(int state) {
}
/**
* Resumes resizing operation on the Pip that was previously suspended.
*
* @param reason The reason resizing operations on the Pip was suspended.
*/
default void resumePipResizing(int reason) {
default void registerSessionListenerForCurrentUser() {
}
/**
@@ -161,14 +108,6 @@ public interface Pip {
*/
default void showPictureInPictureMenu() {}
/**
* Suspends resizing operation on the Pip until {@link #resumePipResizing} is called.
*
* @param reason The reason for suspending resizing operations on the Pip.
*/
default void suspendPipResizing(int reason) {
}
/**
* Called by Launcher when swiping an auto-pip enabled Activity to home starts
* @param componentName {@link ComponentName} represents the Activity entering PiP
@@ -199,4 +138,12 @@ public interface Pip {
* PiP and the Back-from-Edge gesture.
*/
default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
* Dump the current state and information if need.
*
* @param pw The stream to dump information to.
*/
default void dump(PrintWriter pw) {
}
}

View File

@@ -31,6 +31,7 @@ import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.UserHandle;
import androidx.annotation.Nullable;
@@ -74,6 +75,7 @@ public class PipMediaController {
}
private final Context mContext;
private final Handler mMainHandler;
private final MediaSessionManager mMediaSessionManager;
private MediaController mMediaController;
@@ -118,15 +120,16 @@ public class PipMediaController {
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
public PipMediaController(Context context) {
public PipMediaController(Context context, Handler mainHandler) {
mContext = context;
mMainHandler = mainHandler;
IntentFilter mediaControlFilter = new IntentFilter();
mediaControlFilter.addAction(ACTION_PLAY);
mediaControlFilter.addAction(ACTION_PAUSE);
mediaControlFilter.addAction(ACTION_NEXT);
mediaControlFilter.addAction(ACTION_PREV);
mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter,
UserHandle.USER_ALL);
mContext.registerReceiverForAllUsers(mPlayPauseActionReceiver, mediaControlFilter,
null /* permission */, mainHandler);
createMediaActions();
mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
@@ -245,7 +248,7 @@ public class PipMediaController {
public void registerSessionListenerForCurrentUser() {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
UserHandle.CURRENT, null);
UserHandle.CURRENT, mMainHandler);
}
/**
@@ -277,7 +280,7 @@ public class PipMediaController {
}
mMediaController = controller;
if (controller != null) {
controller.registerCallback(mPlaybackChangedListener);
controller.registerCallback(mPlaybackChangedListener, mMainHandler);
}
notifyActionsChanged();
notifyMetadataChanged(getMediaMetadata());

View File

@@ -50,9 +50,7 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.util.Rational;
@@ -64,14 +62,14 @@ import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipUpdateThread;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
@@ -98,12 +96,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
private static final int MSG_RESIZE_IMMEDIATE = 1;
private static final int MSG_RESIZE_ANIMATE = 2;
private static final int MSG_OFFSET_ANIMATE = 3;
private static final int MSG_FINISH_RESIZE = 4;
private static final int MSG_RESIZE_USER = 5;
// Not a complete set of states but serves what we want right now.
private enum State {
UNDEFINED(0),
@@ -135,8 +127,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
}
private final Handler mMainHandler;
private final Handler mUpdateHandler;
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final @NonNull PipMenuController mPipMenuController;
@@ -148,6 +138,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private final Map<IBinder, Configuration> mInitialState = new HashMap<>();
private final Optional<LegacySplitScreen> mSplitScreenOptional;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -183,68 +174,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
};
@SuppressWarnings("unchecked")
private final Handler.Callback mUpdateCallbacks = (msg) -> {
SomeArgs args = (SomeArgs) msg.obj;
Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
switch (msg.what) {
case MSG_RESIZE_IMMEDIATE: {
Rect toBounds = (Rect) args.arg2;
resizePip(toBounds);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
break;
}
case MSG_RESIZE_ANIMATE: {
Rect currentBounds = (Rect) args.arg2;
Rect toBounds = (Rect) args.arg3;
Rect sourceHintRect = (Rect) args.arg4;
float startingAngle = (float) args.arg5;
int duration = args.argi2;
animateResizePip(currentBounds, toBounds, sourceHintRect,
args.argi1 /* direction */, duration, startingAngle);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
break;
}
case MSG_OFFSET_ANIMATE: {
Rect originalBounds = (Rect) args.arg2;
final int offset = args.argi1;
final int duration = args.argi2;
offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
Rect toBounds = new Rect(originalBounds);
toBounds.offset(0, offset);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
break;
}
case MSG_FINISH_RESIZE: {
SurfaceControl.Transaction tx = (SurfaceControl.Transaction) args.arg2;
Rect toBounds = (Rect) args.arg3;
finishResize(tx, toBounds, args.argi1 /* direction */, -1);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
break;
}
case MSG_RESIZE_USER: {
Rect startBounds = (Rect) args.arg2;
Rect toBounds = (Rect) args.arg3;
float degrees = (float) args.arg4;
userResizePip(startBounds, toBounds, degrees);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
break;
}
}
args.recycle();
return true;
};
private ActivityManager.RunningTaskInfo mTaskInfo;
private WindowContainerToken mToken;
private SurfaceControl mLeash;
@@ -276,9 +205,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Optional<LegacySplitScreen> splitScreenOptional,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer) {
mMainHandler = new Handler(Looper.getMainLooper());
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
mPipBoundsState = pipBoundsState;
mPipBoundsAlgorithm = boundsHandler;
mPipMenuController = pipMenuController;
@@ -290,12 +218,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
mSplitScreenOptional = splitScreenOptional;
mTaskOrganizer = shellTaskOrganizer;
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
displayController.addDisplayWindowListener(this);
}
mMainExecutor = mainExecutor;
public Handler getUpdateHandler() {
return mUpdateHandler;
// TODO: Can be removed once wm components are created on the shell-main thread
mMainExecutor.execute(() -> {
mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
});
displayController.addDisplayWindowListener(this);
}
public Rect getCurrentOrAnimatingBounds() {
@@ -428,15 +357,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
// Make sure to grab the latest source hint rect as it could have been updated
// right after applying the windowing mode change.
final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
destinationBounds);
scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
0 /* startingAngle */, sourceHintRect, direction, animationDurationMs,
null /* updateBoundsCallback */);
mState = State.EXITING_PIP;
mMainExecutor.execute(() -> {
t.apply();
// Make sure to grab the latest source hint rect as it could have been
// updated right after applying the windowing mode change.
final Rect sourceHintRect = getValidSourceHintRect(mPictureInPictureParams,
destinationBounds);
scheduleAnimateResizePip(mPipBoundsState.getBounds(), destinationBounds,
0 /* startingAngle */, sourceHintRect, direction,
animationDurationMs, null /* updateBoundsCallback */);
mState = State.EXITING_PIP;
});
}
});
}
@@ -465,12 +396,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
// removePipImmediately is expected when the following animation finishes.
mUpdateHandler.post(() -> mPipAnimationController
mPipAnimationController
.getAnimator(mLeash, mPipBoundsState.getBounds(), 1f, 0f)
.setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration)
.start());
.start();
mInitialState.remove(mToken.asBinder());
mState = State.EXITING_PIP;
}
@@ -579,12 +510,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
tx.setAlpha(mLeash, 0f);
tx.apply();
applyEnterPipSyncTransaction(destinationBounds, () -> {
mUpdateHandler.post(() -> mPipAnimationController
mPipAnimationController
.getAnimator(mLeash, destinationBounds, 0f, 1f)
.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
.start());
.start();
// mState is set right after the animation is kicked off to block any resize
// requests such as offsetPip that may have been called prior to the transition.
mState = State.ENTERING_PIP;
@@ -599,13 +530,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
wct.setBounds(mToken, destinationBounds);
wct.scheduleFinishEnterPip(mToken, destinationBounds);
// TODO: Migrate to SyncTransactionQueue
mTaskOrganizer.applySyncTransaction(wct, new WindowContainerTransactionCallback() {
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
if (runnable != null) {
runnable.run();
}
mMainExecutor.execute(() -> {
t.apply();
if (runnable != null) {
runnable.run();
}
});
}
});
}
@@ -621,12 +555,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mState = State.ENTERING_PIP;
}
final Rect pipBounds = mPipBoundsState.getBounds();
runOnMainHandler(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionStarted(componentName, direction, pipBounds);
}
});
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionStarted(componentName, direction, pipBounds);
}
}
private void sendOnPipTransitionFinished(
@@ -634,29 +566,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (direction == TRANSITION_DIRECTION_TO_PIP) {
mState = State.ENTERED_PIP;
}
runOnMainHandler(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
}
});
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionFinished(mTaskInfo.baseActivity, direction);
}
}
private void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
runOnMainHandler(() -> {
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
}
});
}
private void runOnMainHandler(Runnable r) {
if (Looper.getMainLooper() == Looper.myLooper()) {
r.run();
} else {
mMainHandler.post(r);
for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
callback.onPipTransitionCanceled(mTaskInfo.baseActivity, direction);
}
}
@@ -872,15 +792,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = currentBounds;
args.arg3 = destinationBounds;
args.arg4 = sourceHintRect;
args.arg5 = startingAngle;
args.argi1 = direction;
args.argi2 = durationMs;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
animateResizePip(currentBounds, destinationBounds, sourceHintRect, direction, durationMs,
startingAngle);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(destinationBounds);
}
}
/**
@@ -888,10 +804,24 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
* {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
*/
public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = toBounds;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args));
// Could happen when exitPip
if (mToken == null || mLeash == null) {
Log.w(TAG, "Abort animation, invalid leash");
return;
}
mPipBoundsState.setBounds(toBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mState.isInPip());
if (mPipMenuController.isMenuVisible()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
}
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
}
/**
@@ -909,12 +839,27 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
*/
public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
Consumer<Rect> updateBoundsCallback) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = startBounds;
args.arg3 = toBounds;
args.arg4 = degrees;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_USER, args));
// Could happen when exitPip
if (mToken == null || mLeash == null) {
Log.w(TAG, "Abort animation, invalid leash");
return;
}
if (startBounds.isEmpty() || toBounds.isEmpty()) {
Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
return;
}
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, toBounds, degrees);
if (mPipMenuController.isMenuVisible()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
}
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
}
/**
@@ -948,13 +893,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = createFinishResizeSurfaceTransaction(
destinationBounds);
args.arg3 = destinationBounds;
args.argi1 = direction;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_FINISH_RESIZE, args));
finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
direction, -1);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(destinationBounds);
}
}
private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
@@ -979,20 +922,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = originalBounds;
// offset would be zero if triggered from screen rotation.
args.argi1 = offset;
args.argi2 = duration;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_OFFSET_ANIMATE, args));
offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
Rect toBounds = new Rect(originalBounds);
toBounds.offset(0, offset);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
}
private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleOffsetPip() instead of this "
+ "directly");
}
if (mTaskInfo == null) {
Log.w(TAG, "mTaskInfo is not set");
return;
@@ -1003,62 +941,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
TRANSITION_DIRECTION_SAME, durationMs, 0);
}
private void resizePip(Rect destinationBounds) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
+ "directly");
}
// Could happen when exitPip
if (mToken == null || mLeash == null) {
Log.w(TAG, "Abort animation, invalid leash");
return;
}
mPipBoundsState.setBounds(destinationBounds);
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper
.crop(tx, mLeash, destinationBounds)
.round(tx, mLeash, mState.isInPip());
if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
mPipMenuController.resizePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
}
private void userResizePip(Rect startBounds, Rect destinationBounds, float degrees) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleUserResizePip() instead of "
+ "this directly");
}
// Could happen when exitPip
if (mToken == null || mLeash == null) {
Log.w(TAG, "Abort animation, invalid leash");
return;
}
if (startBounds.isEmpty() || destinationBounds.isEmpty()) {
Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
return;
}
final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds, degrees);
if (mPipMenuController.isMenuVisible()) {
runOnMainHandler(() ->
mPipMenuController.movePipMenu(mLeash, tx, destinationBounds));
} else {
tx.apply();
}
}
private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
@PipAnimationController.TransitionDirection int direction,
@PipAnimationController.AnimationType int type) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleResizePip() instead of this "
+ "directly");
}
mPipBoundsState.setBounds(destinationBounds);
if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
removePipImmediately();
@@ -1097,7 +982,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
mSurfaceTransactionHelper.scale(t, snapshotSurface, snapshotSrc, snapshotDest);
t.apply();
mUpdateHandler.post(() -> {
mMainExecutor.execute(() -> {
// Start animation to fade out the snapshot.
final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
animator.setDuration(mEnterExitAnimationDuration);
@@ -1129,10 +1014,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
private void finishResizeForMenu(Rect destinationBounds) {
runOnMainHandler(() -> {
mPipMenuController.movePipMenu(null, null, destinationBounds);
mPipMenuController.updateMenuBounds(destinationBounds);
});
mPipMenuController.movePipMenu(null, null, destinationBounds);
mPipMenuController.updateMenuBounds(destinationBounds);
}
private void prepareFinishResizeTransaction(Rect destinationBounds,
@@ -1185,10 +1068,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, int durationMs,
float startingAngle) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
+ "this directly");
}
// Could happen when exitPip
if (mToken == null || mLeash == null) {
Log.w(TAG, "Abort animation, invalid leash");

View File

@@ -30,6 +30,7 @@ import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -39,6 +40,7 @@ import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
@@ -97,6 +99,8 @@ public class PhonePipMenuController implements PipMenuController {
private final RectF mTmpDestinationRectF = new RectF();
private final Context mContext;
private final PipMediaController mMediaController;
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
@@ -116,11 +120,14 @@ public class PhonePipMenuController implements PipMenuController {
}
};
public PhonePipMenuController(Context context,
PipMediaController mediaController, SystemWindows systemWindows) {
public PhonePipMenuController(Context context, PipMediaController mediaController,
SystemWindows systemWindows, ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
mMediaController = mediaController;
mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
}
public boolean isMenuVisible() {
@@ -156,7 +163,7 @@ public class PhonePipMenuController implements PipMenuController {
if (mPipMenuView != null) {
detachPipMenuView();
}
mPipMenuView = new PipMenuView(mContext, this);
mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler);
mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);

View File

@@ -21,22 +21,20 @@ import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
import android.app.AppOpsManager;
import android.app.AppOpsManager.OnOpChangedListener;
import android.app.IActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
import android.util.Pair;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils;
public class PipAppOpsListener {
private static final String TAG = PipAppOpsListener.class.getSimpleName();
private Context mContext;
private Handler mHandler;
private IActivityManager mActivityManager;
private ShellExecutor mMainExecutor;
private AppOpsManager mAppOpsManager;
private Callback mCallback;
@@ -53,7 +51,7 @@ public class PipAppOpsListener {
if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
packageName) != MODE_ALLOWED) {
mHandler.post(() -> mCallback.dismissPip());
mMainExecutor.execute(() -> mCallback.dismissPip());
}
}
} catch (NameNotFoundException e) {
@@ -63,11 +61,9 @@ public class PipAppOpsListener {
}
};
public PipAppOpsListener(Context context, IActivityManager activityManager,
Callback callback) {
public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
mContext = context;
mHandler = new Handler(mContext.getMainLooper());
mActivityManager = activityManager;
mMainExecutor = mainExecutor;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mCallback = callback;
}

View File

@@ -71,11 +71,11 @@ import java.util.function.Consumer;
/**
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
public class PipController implements PipTaskOrganizer.PipTransitionCallback {
private static final String TAG = "PipController";
private Context mContext;
private ShellExecutor mMainExecutor;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
private PipInputConsumer mPipInputConsumer;
private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -84,6 +84,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipBoundsState mPipBoundsState;
private PipTouchHandler mTouchHandler;
protected final PipImpl mImpl = new PipImpl();
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private final Rect mTmpInsetBounds = new Rect();
@@ -204,6 +205,28 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
/**
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
public static Pip create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
Slog.w(TAG, "Device doesn't support Pip feature");
return null;
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor)
.mImpl;
}
protected PipController(Context context,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
@@ -235,7 +258,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP);
INPUT_CONSUMER_PIP, mainExecutor);
mPipTaskOrganizer.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
final DisplayInfo newDisplayInfo = new DisplayInfo();
@@ -288,7 +311,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
if (taskInfo != null) {
// If SystemUI restart, and it already existed a pinned stack,
// register the pip input consumer to ensure touch can send to it.
mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
mPipInputConsumer.registerInputConsumer();
}
} catch (RemoteException | UnsupportedOperationException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
@@ -301,12 +324,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
@Override
public void onActivityPinned(String packageName, int userId, int taskId,
int stackId) {
mMainExecutor.execute(() -> {
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mAppOpsListener.onActivityPinned(packageName);
});
mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
mTouchHandler.onActivityPinned();
mMediaController.onActivityPinned();
mAppOpsListener.onActivityPinned(packageName);
mPipInputConsumer.registerInputConsumer();
}
@Override
@@ -314,10 +335,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
final Pair<ComponentName, Integer> topPipActivityInfo =
PipUtils.getTopPipActivity(mContext);
final ComponentName topActivity = topPipActivityInfo.first;
mMainExecutor.execute(() -> {
mTouchHandler.onActivityUnpinned(topActivity);
mAppOpsListener.onActivityUnpinned();
});
mTouchHandler.onActivityUnpinned(topActivity);
mAppOpsListener.onActivityUnpinned();
mPipInputConsumer.unregisterInputConsumer();
}
@@ -333,60 +352,46 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
});
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
mMainExecutor.execute(() -> {
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
});
private void onConfigurationChanged(Configuration newConfig) {
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
}
@Override
public void onDensityOrFontScaleChanged() {
mMainExecutor.execute(() -> {
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
});
private void onDensityOrFontScaleChanged() {
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
}
@Override
public void onOverlayChanged() {
mMainExecutor.execute(() -> {
mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
updateMovementBounds(null /* toBounds */,
false /* fromRotation */, false /* fromImeAdjustment */,
false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
});
private void onOverlayChanged() {
mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
updateMovementBounds(null /* toBounds */,
false /* fromRotation */, false /* fromImeAdjustment */,
false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
}
@Override
public void registerSessionListenerForCurrentUser() {
private void registerSessionListenerForCurrentUser() {
mMediaController.registerSessionListenerForCurrentUser();
}
@Override
public void onSystemUiStateChanged(boolean isValidState, int flag) {
private void onSystemUiStateChanged(boolean isValidState, int flag) {
mTouchHandler.onSystemUiStateChanged(isValidState);
}
/**
* Expands the PIP.
*/
@Override
public void expandPip() {
mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
}
@Override
public PipTouchHandler getPipTouchHandler() {
private PipTouchHandler getPipTouchHandler() {
return mTouchHandler;
}
/**
* Hides the PIP menu.
*/
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
mMenuController.hideMenu(onStartCallback, onEndCallback);
}
@@ -408,9 +413,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
/**
* Sets both shelf visibility and its height.
*/
@Override
public void setShelfHeight(boolean visible, int height) {
mMainExecutor.execute(() -> setShelfHeightLocked(visible, height));
private void setShelfHeight(boolean visible, int height) {
setShelfHeightLocked(visible, height);
}
private void setShelfHeightLocked(boolean visible, int height) {
@@ -418,18 +422,15 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
}
@Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
private void setPinnedStackAnimationType(int animationType) {
mPipTaskOrganizer.setOneShotAnimationType(animationType);
}
@Override
public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback);
private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
mPinnedStackAnimationRecentsCallback = callback;
}
@Override
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, int shelfHeight) {
setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
@@ -438,11 +439,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
pictureInPictureParams);
}
@Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
mPipTaskOrganizer.stopSwipePipToHome(componentName, destinationBounds);
}
/**
* Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
* Back-gesture handler, to avoid conflicting with PiP when it's stashed.
*/
private void setPipExclusionBoundsChangeListener(
Consumer<Rect> pipExclusionBoundsChangeListener) {
mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
}
@Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) {
@@ -468,16 +477,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
/**
* Set a listener to watch out for PiP bounds. This is mostly used by SystemUI's
* Back-gesture handler, to avoid conflicting with PiP when it's stashed.
*/
@Override
public void setPipExclusionBoundsChangeListener(
Consumer<Rect> pipExclusionBoundsChangeListener) {
mTouchHandler.setPipExclusionBoundsChangeListener(pipExclusionBoundsChangeListener);
}
@Override
public void onPipTransitionFinished(ComponentName activity, int direction) {
onPipTransitionFinishedOrCanceled(direction);
@@ -607,8 +606,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}
}
@Override
public void dump(PrintWriter pw) {
private void dump(PrintWriter pw) {
final String innerPrefix = " ";
pw.println(TAG);
mMenuController.dump(pw, innerPrefix);
@@ -619,23 +617,123 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipInputConsumer.dump(pw, innerPrefix);
}
/**
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
public static PipController create(Context context, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
Slog.w(TAG, "Device doesn't support Pip feature");
return null;
private class PipImpl implements Pip {
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
mMainExecutor.execute(() -> {
PipController.this.hidePipMenu(onStartCallback, onEndCallback);
});
}
return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer,
pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor);
@Override
public void expandPip() {
mMainExecutor.execute(() -> {
PipController.this.expandPip();
});
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
mMainExecutor.execute(() -> {
PipController.this.onConfigurationChanged(newConfig);
});
}
@Override
public void onDensityOrFontScaleChanged() {
mMainExecutor.execute(() -> {
PipController.this.onDensityOrFontScaleChanged();
});
}
@Override
public void onOverlayChanged() {
mMainExecutor.execute(() -> {
PipController.this.onOverlayChanged();
});
}
@Override
public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
mMainExecutor.execute(() -> {
PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
});
}
@Override
public void registerSessionListenerForCurrentUser() {
mMainExecutor.execute(() -> {
PipController.this.registerSessionListenerForCurrentUser();
});
}
@Override
public void setShelfHeight(boolean visible, int height) {
mMainExecutor.execute(() -> {
PipController.this.setShelfHeight(visible, height);
});
}
@Override
public void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationListener(callback);
});
}
@Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationType(animationType);
});
}
@Override
public void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) {
mMainExecutor.execute(() -> {
PipController.this.setPipExclusionBoundsChangeListener(listener);
});
}
@Override
public void showPictureInPictureMenu() {
mMainExecutor.execute(() -> {
PipController.this.showPictureInPictureMenu();
});
}
@Override
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, int launcherRotation,
int shelfHeight) {
Rect[] result = new Rect[1];
try {
mMainExecutor.executeBlocking(() -> {
result[0] = PipController.this.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams, launcherRotation, shelfHeight);
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to start swipe pip to home");
}
return result[0];
}
@Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
mMainExecutor.execute(() -> {
PipController.this.stopSwipePipToHome(componentName, destinationBounds);
});
}
@Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
PipController.this.dump(pw);
});
} catch (InterruptedException e) {
Slog.e(TAG, "Failed to dump PipController in 2s");
}
}
}
}

View File

@@ -37,9 +37,12 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.DismissCircleView;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipUiEventLogger;
import java.util.concurrent.TimeUnit;
import kotlin.Unit;
/**
@@ -57,36 +60,32 @@ public class PipDismissTargetHandler {
* MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
* PIP.
*/
private final MagnetizedObject<Rect> mMagnetizedPip;
private MagnetizedObject<Rect> mMagnetizedPip;
/**
* Container for the dismiss circle, so that it can be animated within the container via
* translation rather than within the WindowManager via slow layout animations.
*/
private final ViewGroup mTargetViewContainer;
private ViewGroup mTargetViewContainer;
/** Circle view used to render the dismiss target. */
private final DismissCircleView mTargetView;
private DismissCircleView mTargetView;
/**
* MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
*/
private final MagnetizedObject.MagneticTarget mMagneticTarget;
private MagnetizedObject.MagneticTarget mMagneticTarget;
/** PhysicsAnimator instance for animating the dismiss target in/out. */
private final PhysicsAnimator<View> mMagneticTargetAnimator;
/**
* PhysicsAnimator instance for animating the dismiss target in/out.
*/
private PhysicsAnimator<View> mMagneticTargetAnimator;
/** Default configuration to use for springing the dismiss target in/out. */
private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
new PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
/**
* Runnable that can be posted delayed to show the target. This needs to be saved as a member
* variable so we can pass it to removeCallbacks.
*/
private Runnable mShowTargetAction = this::showDismissTargetMaybe;
// Allow dragging the PIP to a location to close it
private final boolean mEnableDismissDragToEdge;
@@ -96,74 +95,76 @@ public class PipDismissTargetHandler {
private final PipMotionHelper mMotionHelper;
private final PipUiEventLogger mPipUiEventLogger;
private final WindowManager mWindowManager;
private final Handler mHandler;
private final ShellExecutor mMainExecutor;
public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
PipMotionHelper motionHelper, Handler handler) {
PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
mContext = context;
mPipUiEventLogger = pipUiEventLogger;
mMotionHelper = motionHelper;
mHandler = handler;
mMainExecutor = mainExecutor;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Resources res = context.getResources();
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
mTargetView = new DismissCircleView(context);
mTargetViewContainer = new FrameLayout(context);
mTargetViewContainer.setBackgroundDrawable(
context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
mTargetViewContainer.setClipChildren(false);
mTargetViewContainer.addView(mTargetView);
mMainExecutor.execute(() -> {
mTargetView = new DismissCircleView(context);
mTargetViewContainer = new FrameLayout(context);
mTargetViewContainer.setBackgroundDrawable(
context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
mTargetViewContainer.setClipChildren(false);
mTargetViewContainer.addView(mTargetView);
mMagnetizedPip = mMotionHelper.getMagnetizedPip();
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
updateMagneticTargetSize();
mMagnetizedPip = mMotionHelper.getMagnetizedPip();
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
updateMagneticTargetSize();
mMagnetizedPip.setAnimateStuckToTarget(
(target, velX, velY, flung, after) -> {
mMagnetizedPip.setAnimateStuckToTarget(
(target, velX, velY, flung, after) -> {
if (mEnableDismissDragToEdge) {
mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung,
after);
}
return Unit.INSTANCE;
});
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
// Show the dismiss target, in case the initial touch event occurred within
// the magnetic field radius.
if (mEnableDismissDragToEdge) {
mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after);
showDismissTargetMaybe();
}
return Unit.INSTANCE;
});
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
// Show the dismiss target, in case the initial touch event occurred within the
// magnetic field radius.
if (mEnableDismissDragToEdge) {
showDismissTargetMaybe();
}
}
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velX, float velY, boolean wasFlungOut) {
if (wasFlungOut) {
mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
hideDismissTargetMaybe();
} else {
mMotionHelper.setSpringingToTouch(true);
@Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velX, float velY, boolean wasFlungOut) {
if (wasFlungOut) {
mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
hideDismissTargetMaybe();
} else {
mMotionHelper.setSpringingToTouch(true);
}
}
}
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mMotionHelper.notifyDismissalPending();
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mMainExecutor.executeDelayed(() -> {
mMotionHelper.notifyDismissalPending();
mMotionHelper.animateDismiss();
hideDismissTargetMaybe();
handler.post(() -> {
mMotionHelper.animateDismiss();
hideDismissTargetMaybe();
});
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
}, 0);
}
});
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
}
mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
});
mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
}
/**
@@ -200,7 +201,6 @@ public class PipDismissTargetHandler {
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
public void createOrUpdateDismissTarget() {
if (!mTargetViewContainer.isAttachedToWindow()) {
mHandler.removeCallbacks(mShowTargetAction);
mMagneticTargetAnimator.cancel();
mTargetViewContainer.setVisibility(View.INVISIBLE);
@@ -270,7 +270,6 @@ public class PipDismissTargetHandler {
return;
}
mHandler.removeCallbacks(mShowTargetAction);
mMagneticTargetAnimator
.spring(DynamicAnimation.TRANSLATION_Y,
mTargetViewContainer.getHeight(),
@@ -286,8 +285,6 @@ public class PipDismissTargetHandler {
* Removes the dismiss target and cancels any pending callbacks to show it.
*/
public void cleanUpDismissTarget() {
mHandler.removeCallbacks(mShowTargetAction);
if (mTargetViewContainer.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(mTargetViewContainer);
}

View File

@@ -29,6 +29,8 @@ import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.InputEvent;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
/**
@@ -81,6 +83,7 @@ public class PipInputConsumer {
private final IWindowManager mWindowManager;
private final IBinder mToken;
private final String mName;
private final ShellExecutor mMainExecutor;
private InputEventReceiver mInputEventReceiver;
private InputListener mListener;
@@ -89,10 +92,12 @@ public class PipInputConsumer {
/**
* @param name the name corresponding to the input consumer that is defined in the system.
*/
public PipInputConsumer(IWindowManager windowManager, String name) {
public PipInputConsumer(IWindowManager windowManager, String name,
ShellExecutor mainExecutor) {
mWindowManager = windowManager;
mToken = new Binder();
mName = name;
mMainExecutor = mainExecutor;
}
/**
@@ -107,9 +112,11 @@ public class PipInputConsumer {
*/
public void setRegistrationListener(RegistrationListener listener) {
mRegistrationListener = listener;
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
}
mMainExecutor.execute(() -> {
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
}
});
}
/**
@@ -125,14 +132,6 @@ public class PipInputConsumer {
* Registers the input consumer.
*/
public void registerInputConsumer() {
registerInputConsumer(false);
}
/**
* Registers the input consumer.
* @param withSfVsync the flag set using sf vsync signal or no
*/
public void registerInputConsumer(boolean withSfVsync) {
if (mInputEventReceiver != null) {
return;
}
@@ -144,11 +143,15 @@ public class PipInputConsumer {
} catch (RemoteException e) {
Log.e(TAG, "Failed to create input consumer", e);
}
mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(),
withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
mMainExecutor.execute(() -> {
// Choreographer.getSfInstance() must be called on the thread that the input event
// receiver should be receiving events
mInputEventReceiver = new InputEventReceiver(inputChannel,
mMainExecutor.getLooper(), Choreographer.getSfInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
});
}
/**
@@ -166,9 +169,11 @@ public class PipInputConsumer {
}
mInputEventReceiver.dispose();
mInputEventReceiver = null;
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
}
mMainExecutor.execute(() -> {
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
}
});
}
public void dump(PrintWriter pw, String prefix) {

View File

@@ -63,6 +63,7 @@ import android.widget.LinearLayout;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils;
import java.util.ArrayList;
@@ -116,7 +117,8 @@ public class PipMenuView extends FrameLayout {
}
};
private Handler mHandler = new Handler();
private ShellExecutor mMainExecutor;
private Handler mMainHandler;
private final Runnable mHideMenuRunnable = this::hideMenu;
@@ -127,10 +129,13 @@ public class PipMenuView extends FrameLayout {
protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
public PipMenuView(Context context, PhonePipMenuController controller) {
public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler) {
super(context, null, 0);
mContext = context;
mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
inflate(context, R.layout.pip_menu, this);
@@ -412,17 +417,15 @@ public class PipMenuView extends FrameLayout {
d.setTint(Color.WHITE);
actionView.setImageDrawable(d);
}
}, mHandler);
}, mMainHandler);
actionView.setContentDescription(action.getContentDescription());
if (action.isEnabled()) {
actionView.setOnClickListener(v -> {
mHandler.post(() -> {
try {
action.getActionIntent().send();
} catch (CanceledException e) {
Log.w(TAG, "Failed to send action", e);
}
});
try {
action.getActionIntent().send();
} catch (CanceledException e) {
Log.w(TAG, "Failed to send action", e);
}
});
}
actionView.setEnabled(action.isEnabled());
@@ -480,13 +483,13 @@ public class PipMenuView extends FrameLayout {
}
private void cancelDelayedHide() {
mHandler.removeCallbacks(mHideMenuRunnable);
mMainExecutor.removeCallbacks(mHideMenuRunnable);
}
private void repostDelayedHide(int delay) {
int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
mHandler.removeCallbacks(mHideMenuRunnable);
mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout);
mMainExecutor.removeCallbacks(mHideMenuRunnable);
mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
}
}

View File

@@ -40,6 +40,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -74,8 +75,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private PhonePipMenuController mMenuController;
private PipSnapAlgorithm mSnapAlgorithm;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
/** The region that all of PIP must stay within. */
private final Rect mFloatingAllowedArea = new Rect();
@@ -130,10 +129,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
mMainHandler.post(() -> {
mMenuController.updateMenuLayout(newBounds);
mPipBoundsState.setBounds(newBounds);
});
mMenuController.updateMenuLayout(newBounds);
mPipBoundsState.setBounds(newBounds);
};
/**
@@ -174,7 +171,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) {
PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator,
ShellExecutor mainExecutor) {
mContext = context;
mPipTaskOrganizer = pipTaskOrganizer;
mPipBoundsState = pipBoundsState;
@@ -184,8 +182,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion());
mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
mSfAnimationHandlerThreadLocal.get());
// Need to get the shell main thread sf vsync animation handler
mainExecutor.execute(() -> {
mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler(
mSfAnimationHandlerThreadLocal.get());
});
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -256,10 +258,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
(Rect newBounds) -> {
mMainHandler.post(() -> {
mMenuController.updateMenuLayout(newBounds);
});
});
});
}
} else {
// If PIP is 'catching up' after being stuck in the dismiss target, update the animation
@@ -326,11 +326,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
}
cancelPhysicsAnimation();
mMenuController.hideMenuWithoutResize();
mPipTaskOrganizer.getUpdateHandler().post(() -> {
mPipTaskOrganizer.exitPip(skipAnimation
? 0
: LEAVE_PIP_DURATION);
});
mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
}
/**
@@ -393,7 +389,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
.spring(FloatProperties.RECT_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
.flingThenSpring(
FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX,
FloatProperties.RECT_X, velocityX,
isStash ? mStashConfigX : mFlingConfigX,
mSpringConfig, true /* flingMustReachMinOrMax */)
.flingThenSpring(
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);

View File

@@ -29,7 +29,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.view.BatchedInputEventReceiver;
@@ -45,6 +44,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -52,7 +52,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
/**
@@ -73,7 +73,7 @@ public class PipResizeGestureHandler {
private final PhonePipMenuController mPhonePipMenuController;
private final PipUiEventLogger mPipUiEventLogger;
private final int mDisplayId;
private final Executor mMainExecutor;
private final ShellExecutor mMainExecutor;
private final Region mTmpRegion = new Region();
private final PointF mDownPoint = new PointF();
@@ -91,7 +91,6 @@ public class PipResizeGestureHandler {
private final Rect mDisplayBounds = new Rect();
private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private final Handler mHandler;
private int mDelta;
private float mTouchSlop;
@@ -119,10 +118,10 @@ public class PipResizeGestureHandler {
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
Runnable updateMovementBoundsRunnable, PipUiEventLogger pipUiEventLogger,
PhonePipMenuController menuActivityController) {
PhonePipMenuController menuActivityController, ShellExecutor mainExecutor) {
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
mMainExecutor = mainExecutor;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
@@ -131,7 +130,6 @@ public class PipResizeGestureHandler {
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mHandler = new Handler(Looper.getMainLooper());
context.getDisplay().getRealSize(mMaxSize);
reloadResources();
@@ -140,7 +138,8 @@ public class PipResizeGestureHandler {
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_PINCH_RESIZE,
/* defaultValue = */ false);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mMainExecutor,
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mMainExecutor,
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
@@ -213,8 +212,8 @@ public class PipResizeGestureHandler {
// Register input event receiver
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"pip-resize", mDisplayId);
mInputEventReceiver = new SysUiInputEventReceiver(
mInputMonitor.getInputChannel(), Looper.getMainLooper());
mInputEventReceiver = new PipResizeInputEventReceiver(
mInputMonitor.getInputChannel(), mMainExecutor.getLooper());
}
}
@@ -523,7 +522,7 @@ public class PipResizeGestureHandler {
private void finishResize() {
if (!mLastResizeBounds.isEmpty()) {
final Runnable callback = () -> {
final Consumer<Rect> callback = (rect) -> {
mUserResizeBounds.set(mLastResizeBounds);
mMotionHelper.synchronizePinnedStackBounds();
mUpdateMovementBoundsRunnable.run();
@@ -537,16 +536,10 @@ public class PipResizeGestureHandler {
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds,
mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds()));
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
PINCH_RESIZE_SNAP_DURATION, mAngle,
(Rect rect) -> {
mHandler.post(callback);
});
PINCH_RESIZE_SNAP_DURATION, -mAngle, callback);
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE,
(Rect bounds) -> {
mHandler.post(callback);
});
PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE, callback);
}
mPipUiEventLogger.log(
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
@@ -593,8 +586,8 @@ public class PipResizeGestureHandler {
pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
}
class SysUiInputEventReceiver extends BatchedInputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper) {
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
super(channel, looper, Choreographer.getSfInstance());
}

View File

@@ -167,18 +167,20 @@ public class PipTouchHandler {
mGesture = new DefaultPipTouchGesture();
mMotionHelper = new PipMotionHelper(mContext, pipBoundsState, pipTaskOrganizer,
mMenuController, mPipBoundsAlgorithm.getSnapAlgorithm(),
floatingContentCoordinator);
floatingContentCoordinator, mainExecutor);
mPipResizeGestureHandler =
new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
mMotionHelper, pipTaskOrganizer, this::getMovementBounds,
this::updateMovementBounds, pipUiEventLogger, menuController);
this::updateMovementBounds, pipUiEventLogger, menuController,
mainExecutor);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mHandler);
mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> mMenuController.showMenuWithDelay(MENU_STATE_FULL,
mPipBoundsState.getBounds(), true /* allowMenuTimeout */, willResizeMenu(),
shouldShowResizeHandle()),
menuController::hideMenu);
menuController::hideMenu,
mainExecutor);
Resources res = context.getResources();
mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -196,7 +198,7 @@ public class PipTouchHandler {
PIP_STASHING,
/* defaultValue = */ true);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
context.getMainExecutor(),
mainExecutor,
properties -> {
if (properties.getKeyset().contains(PIP_STASHING)) {
mEnableStash = properties.getBoolean(

View File

@@ -25,6 +25,7 @@ import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter;
@@ -39,7 +40,7 @@ public class PipTouchState {
public static final long DOUBLE_TAP_TIMEOUT = 200;
static final long HOVER_EXIT_TIMEOUT = 50;
private final Handler mHandler;
private final ShellExecutor mMainExecutor;
private final ViewConfiguration mViewConfig;
private final Runnable mDoubleTapTimeoutCallback;
private final Runnable mHoverExitTimeoutCallback;
@@ -67,12 +68,12 @@ public class PipTouchState {
private int mActivePointerId;
private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
public PipTouchState(ViewConfiguration viewConfig, Handler handler,
Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) {
public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
mViewConfig = viewConfig;
mHandler = handler;
mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
mMainExecutor = mainExecutor;
}
/**
@@ -116,7 +117,7 @@ public class PipTouchState {
mIsDragging = false;
mLastDownTouchTime = mDownTouchTime;
if (mDoubleTapTimeoutCallback != null) {
mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
}
break;
}
@@ -324,8 +325,8 @@ public class PipTouchState {
public void scheduleDoubleTapTimeoutCallback() {
if (mIsWaitingForDoubleTap) {
long delay = getDoubleTapTimeoutCallbackDelay();
mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
mHandler.postDelayed(mDoubleTapTimeoutCallback, delay);
mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
}
}
@@ -342,17 +343,17 @@ public class PipTouchState {
*/
public void removeDoubleTapTimeoutCallback() {
mIsWaitingForDoubleTap = false;
mHandler.removeCallbacks(mDoubleTapTimeoutCallback);
mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
}
@VisibleForTesting
public void scheduleHoverExitTimeoutCallback() {
mHandler.removeCallbacks(mHoverExitTimeoutCallback);
mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
}
void removeHoverExitTimeoutCallback() {
mHandler.removeCallbacks(mHoverExitTimeoutCallback);
mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
}
void addMovementToVelocityTracker(MotionEvent event) {

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2020 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.pip.phone;
import android.os.Handler;
import android.os.HandlerThread;
/**
* Similar to {@link com.android.internal.os.BackgroundThread}, this is a shared singleton
* foreground thread for each process for updating PIP.
*/
public final class PipUpdateThread extends HandlerThread {
private static PipUpdateThread sInstance;
private static Handler sHandler;
private PipUpdateThread() {
super("pip");
}
private static void ensureThreadLocked() {
if (sInstance == null) {
sInstance = new PipUpdateThread();
sInstance.start();
sHandler = new Handler(sInstance.getLooper());
}
}
/**
* @return the static update thread instance
*/
public static PipUpdateThread get() {
synchronized (PipUpdateThread.class) {
ensureThreadLocked();
return sInstance;
}
}
/**
* @return the static update thread handler instance
*/
public static Handler getHandler() {
synchronized (PipUpdateThread.class) {
ensureThreadLocked();
return sHandler;
}
}
}

View File

@@ -36,6 +36,7 @@ import android.view.DisplayInfo;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
@@ -51,7 +52,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
public class TvPipController implements PipTaskOrganizer.PipTransitionCallback,
TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
private static final String TAG = "TvPipController";
static final boolean DEBUG = true;
@@ -90,13 +91,15 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private final ShellExecutor mMainExecutor;
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private int mResizeAnimationDuration;
public TvPipController(
public static Pip create(
Context context,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -105,8 +108,34 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper wmShell) {
WindowManagerShellWrapper wmShell,
ShellExecutor mainExecutor) {
return new TvPipController(
context,
pipBoundsState,
pipBoundsAlgorithm,
pipTaskOrganizer,
tvPipMenuController,
pipMediaController,
pipNotificationController,
taskStackListener,
wmShell,
mainExecutor).mImpl;
}
private TvPipController(
Context context,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper wmShell,
ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
mPipBoundsState = pipBoundsState;
mPipBoundsState.setDisplayInfo(getDisplayInfo());
@@ -129,8 +158,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
registerWmShellPinnedStackListener(wmShell);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
private void onConfigurationChanged(Configuration newConfig) {
if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
if (isPipShown()) {
@@ -145,8 +173,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
/**
* Returns {@code true} if Pip is shown.
*/
@Override
public boolean isPipShown() {
private boolean isPipShown() {
return mState != STATE_NO_PIP;
}
@@ -211,8 +238,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
* @param state the to determine the Pip bounds. IMPORTANT: should always match the current
* state of the Controller.
*/
@Override
public void resizePinnedStack(@State int state) {
private void resizePinnedStack(@State int state) {
if (state != mState) {
throw new IllegalArgumentException("The passed state should match the current state!");
}
@@ -240,8 +266,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
}
@Override
public void registerSessionListenerForCurrentUser() {
private void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
@@ -418,4 +443,20 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
throw new IllegalArgumentException("Unknown state " + state);
}
}
private class TvPipImpl implements Pip {
@Override
public void onConfigurationChanged(Configuration newConfig) {
mMainExecutor.execute(() -> {
TvPipController.this.onConfigurationChanged(newConfig);
});
}
@Override
public void registerSessionListenerForCurrentUser() {
mMainExecutor.execute(() -> {
TvPipController.this.registerSessionListenerForCurrentUser();
});
}
}
}

View File

@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.util.Log;
import android.view.SurfaceControl;
@@ -47,6 +48,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final Context mContext;
private final SystemWindows mSystemWindows;
private final PipBoundsState mPipBoundsState;
private final Handler mMainHandler;
private Delegate mDelegate;
private SurfaceControl mLeash;
@@ -56,10 +58,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final List<RemoteAction> mAppActions = new ArrayList<>();
public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController) {
SystemWindows systemWindows, PipMediaController pipMediaController,
Handler mainHandler) {
mContext = context;
mPipBoundsState = pipBoundsState;
mSystemWindows = systemWindows;
mMainHandler = mainHandler;
// We need to "close" the menu the platform call for all the system dialogs to close (for
// example, on the Home button press).
@@ -69,8 +73,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
hideMenu();
}
};
context.registerReceiver(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
mainHandler);
pipMediaController.addActionListener(this::onMediaActionsChanged);
}
@@ -199,9 +204,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
if (!mAppActions.isEmpty()) {
mMenuView.setAdditionalActions(mAppActions);
mMenuView.setAdditionalActions(mAppActions, mMainHandler);
} else {
mMenuView.setAdditionalActions(mMediaActions);
mMenuView.setAdditionalActions(mMediaActions, mMainHandler);
}
}

View File

@@ -49,7 +49,7 @@ import java.util.List;
/**
* A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
* actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
* a {@link #setAdditionalActions(List)} call.
* a {@link #setAdditionalActions(List, Handler)} call.
*/
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView";
@@ -57,7 +57,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final float DISABLED_ACTION_ALPHA = 0.54f;
private final Handler mUiThreadHandler;
private final Animator mFadeInAnimation;
private final Animator mFadeOutAnimation;
@Nullable private Listener mListener;
@@ -80,7 +79,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mUiThreadHandler = new Handler(Looper.getMainLooper());
inflate(context, R.layout.tv_pip_menu, this);
@@ -132,7 +130,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
}
void setAdditionalActions(List<RemoteAction> actions) {
void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
if (DEBUG) Log.d(TAG, "setAdditionalActions()");
// Make sure we exactly as many additional buttons as we have actions to display.
@@ -176,7 +174,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
action.getIcon().loadDrawableAsync(mContext, drawable -> {
drawable.setTint(Color.WHITE);
button.setImageDrawable(drawable);
}, mUiThreadHandler);
}, mainHandler);
}
}

View File

@@ -27,6 +27,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.os.Handler;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
@@ -60,6 +61,7 @@ public class TvPipNotificationController {
private final NotificationManager mNotificationManager;
private final Notification.Builder mNotificationBuilder;
private final ActionBroadcastReceiver mActionBroadcastReceiver;
private final Handler mMainHandler;
private Delegate mDelegate;
private String mDefaultTitle;
@@ -70,10 +72,12 @@ public class TvPipNotificationController {
private String mMediaTitle;
private Bitmap mArt;
public TvPipNotificationController(Context context, PipMediaController pipMediaController) {
public TvPipNotificationController(Context context, PipMediaController pipMediaController,
Handler mainHandler) {
mContext = context;
mPackageManager = context.getPackageManager();
mNotificationManager = context.getSystemService(NotificationManager.class);
mMainHandler = mainHandler;
mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL)
.setLocalOnly(true)
@@ -219,7 +223,8 @@ public class TvPipNotificationController {
void register() {
if (mRegistered) return;
mContext.registerReceiver(this, mIntentFilter, UserHandle.USER_ALL);
mContext.registerReceiverForAllUsers(this, mIntentFilter, null /* permission */,
mMainHandler);
mRegistered = true;
}

View File

@@ -46,7 +46,7 @@ public class TestShellExecutor implements ShellExecutor {
@Override
public boolean hasCallback(Runnable r) {
return !mRunnables.isEmpty();
return mRunnables.contains(r);
}
@Override
@@ -55,8 +55,8 @@ public class TestShellExecutor implements ShellExecutor {
}
public void flushAll() {
for (int i = mRunnables.size() - 1; i >= 0; --i) {
mRunnables.get(i).run();
for (Runnable r : mRunnables) {
r.run();
}
mRunnables.clear();
}

View File

@@ -43,7 +43,9 @@ import android.window.WindowContainerToken;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -71,6 +73,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState;
private ComponentName mComponent1;
@@ -82,10 +85,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext);
mMainExecutor = new TestShellExecutor();
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
mMockPipUiEventLogger, mMockShellTaskOrganizer));
mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
mMainExecutor.flushAll();
preparePipTaskOrg();
}

View File

@@ -24,18 +24,15 @@ import static android.view.MotionEvent.ACTION_UP;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import org.junit.Before;
import org.junit.Test;
@@ -45,23 +42,22 @@ import java.util.concurrent.CountDownLatch;
@RunWith(AndroidTestingRunner.class)
@SmallTest
@RunWithLooper
public class PipTouchStateTest extends ShellTestCase {
private PipTouchState mTouchState;
private CountDownLatch mDoubleTapCallbackTriggeredLatch;
private CountDownLatch mHoverExitCallbackTriggeredLatch;
private TestShellExecutor mShellMainExecutor;
@Before
public void setUp() throws Exception {
mShellMainExecutor = new TestShellExecutor();
mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
mHoverExitCallbackTriggeredLatch = new CountDownLatch(1);
mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
Handler.createAsync(Looper.myLooper()), () -> {
mDoubleTapCallbackTriggeredLatch.countDown();
}, () -> {
mHoverExitCallbackTriggeredLatch.countDown();
});
mDoubleTapCallbackTriggeredLatch::countDown,
mHoverExitCallbackTriggeredLatch::countDown,
mShellMainExecutor);
assertFalse(mTouchState.isDoubleTap());
assertFalse(mTouchState.isWaitingForDoubleTap());
}
@@ -91,9 +87,7 @@ public class PipTouchStateTest extends ShellTestCase {
assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
mTouchState.scheduleDoubleTapTimeoutCallback();
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
Thread.sleep(15);
TestableLooper.get(this).processAllMessages();
mShellMainExecutor.flushAll();
assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
}
@@ -128,17 +122,13 @@ public class PipTouchStateTest extends ShellTestCase {
@Test
public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception {
mTouchState.scheduleHoverExitTimeoutCallback();
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
Thread.sleep(50);
TestableLooper.get(this).processAllMessages();
mShellMainExecutor.flushAll();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0);
}
@Test
public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception {
mTouchState.scheduleHoverExitTimeoutCallback();
TestableLooper.get(this).processAllMessages();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
}
@@ -147,14 +137,12 @@ public class PipTouchStateTest extends ShellTestCase {
mTouchState.scheduleHoverExitTimeoutCallback();
mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(),
0, 0));
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
Thread.sleep(50);
TestableLooper.get(this).processAllMessages();
mShellMainExecutor.flushAll();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
}
private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
return MotionEvent.obtain(0, eventTime, action, x, y, 0);
}
}

View File

@@ -17,13 +17,16 @@
package com.android.systemui.wmshell;
import android.content.Context;
import android.os.Handler;
import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
@@ -57,9 +60,10 @@ public abstract class TvPipModule {
PipMediaController pipMediaController,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper windowManagerShellWrapper) {
WindowManagerShellWrapper windowManagerShellWrapper,
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.of(
new TvPipController(
TvPipController.create(
context,
pipBoundsState,
pipBoundsAlgorithm,
@@ -68,7 +72,8 @@ public abstract class TvPipModule {
pipMediaController,
tvPipNotificationController,
taskStackListener,
windowManagerShellWrapper));
windowManagerShellWrapper,
mainExecutor));
}
@WMSingleton
@@ -84,21 +89,26 @@ public abstract class TvPipModule {
return new PipBoundsState(context);
}
// Handler needed for loadDrawableAsync() in PipControlsViewController
@WMSingleton
@Provides
static TvPipMenuController providesTvPipMenuController(
Context context,
PipBoundsState pipBoundsState,
SystemWindows systemWindows,
PipMediaController pipMediaController) {
return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController);
PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController,
mainHandler);
}
// Handler needed for registerReceiverForAllUsers()
@WMSingleton
@Provides
static TvPipNotificationController provideTvPipNotificationController(Context context,
PipMediaController pipMediaController) {
return new TvPipNotificationController(context, pipMediaController);
PipMediaController pipMediaController,
@ShellMainThread Handler mainHandler) {
return new TvPipNotificationController(context, pipMediaController, mainHandler);
}
@WMSingleton
@@ -109,9 +119,10 @@ public abstract class TvPipModule {
PipBoundsAlgorithm pipBoundsAlgorithm,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
tvPipMenuController, pipSurfaceTransactionHelper, splitScreenOptional,
displayController, pipUiEventLogger, shellTaskOrganizer);
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
}

View File

@@ -250,14 +250,17 @@ public abstract class WMShellBaseModule {
@Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
IActivityManager activityManager,
PipTouchHandler pipTouchHandler) {
return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor);
}
// Needs handler for registering broadcast receivers
@WMSingleton
@Provides
static PipMediaController providePipMediaController(Context context) {
return new PipMediaController(context);
static PipMediaController providePipMediaController(Context context,
@ShellMainThread Handler mainHandler) {
return new PipMediaController(context, mainHandler);
}
@WMSingleton

View File

@@ -19,6 +19,7 @@ package com.android.systemui.wmshell;
import android.animation.AnimationHandler;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
import com.android.systemui.dagger.WMSingleton;
@@ -125,11 +126,15 @@ public class WMShellModule {
return new PipBoundsAlgorithm(context, pipBoundsState);
}
// Handler is used by Icon.loadDrawableAsync
@WMSingleton
@Provides
static PhonePipMenuController providesPipPhoneMenuController(Context context,
PipMediaController pipMediaController, SystemWindows systemWindows) {
return new PhonePipMenuController(context, pipMediaController, systemWindows);
PipMediaController pipMediaController, SystemWindows systemWindows,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipMediaController, systemWindows,
mainExecutor, mainHandler);
}
@WMSingleton
@@ -154,9 +159,10 @@ public class WMShellModule {
PhonePipMenuController menuPhoneController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional,
displayController, pipUiEventLogger, shellTaskOrganizer);
displayController, pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
}

View File

@@ -63,7 +63,6 @@ public class WMShellTest extends SysuiTestCase {
@Mock ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
@Mock PipTouchHandler mPipTouchHandler;
@Mock LegacySplitScreen mLegacySplitScreen;
@Mock OneHanded mOneHanded;
@Mock HideDisplayCutout mHideDisplayCutout;
@@ -80,8 +79,6 @@ public class WMShellTest extends SysuiTestCase {
Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
mKeyguardUpdateMonitor, mNavigationModeController,
mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor);
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
}
@Test