Merge "6/ Update pip to use shell main thread" into sc-dev

This commit is contained in:
Winson Chung
2021-01-27 17:42:49 +00:00
committed by Android (Google) Code Review
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. * animator is under test.
*/ */
internal fun startInternal() { 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() val target = weakTarget.get()
if (target == null) { if (target == null) {
Log.w(TAG, "Trying to animate a GC-ed object.") Log.w(TAG, "Trying to animate a GC-ed object.")

View File

@@ -17,10 +17,15 @@
package com.android.wm.shell.common; package com.android.wm.shell.common;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit; 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. * 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)}. * 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}. * See {@link android.os.Handler#removeCallbacks}.
*/ */
void removeCallbacks(Runnable r); void removeCallbacks(Runnable runnable);
/** /**
* See {@link android.os.Handler#hasCallbacks(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. * Returns the looper that this executor is running on.

View File

@@ -34,54 +34,17 @@ import java.util.function.Consumer;
*/ */
@ExternalThread @ExternalThread
public interface Pip { 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. * Expand PIP, it's possible that specific request to activate the window via Alt-tab.
*/ */
default void expandPip() { 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. * Hides the PIP menu.
*/ */
default void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {} 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. * Called when configuration is changed.
*/ */
@@ -100,12 +63,6 @@ public interface Pip {
default void onOverlayChanged() { default void onOverlayChanged() {
} }
/**
* Registers the session listener for the current user.
*/
default void registerSessionListenerForCurrentUser() {
}
/** /**
* Called when SysUI state changed. * Called when SysUI state changed.
* *
@@ -116,19 +73,9 @@ public interface Pip {
} }
/** /**
* Resize the Pip to the appropriate size for the input state. * Registers the session listener for the current user.
*
* @param state In Pip state also used to determine the new size for the Pip.
*/ */
default void resizePinnedStack(int state) { default void registerSessionListenerForCurrentUser() {
}
/**
* 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) {
} }
/** /**
@@ -161,14 +108,6 @@ public interface Pip {
*/ */
default void showPictureInPictureMenu() {} 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 * Called by Launcher when swiping an auto-pip enabled Activity to home starts
* @param componentName {@link ComponentName} represents the Activity entering PiP * @param componentName {@link ComponentName} represents the Activity entering PiP
@@ -199,4 +138,12 @@ public interface Pip {
* PiP and the Back-from-Edge gesture. * PiP and the Back-from-Edge gesture.
*/ */
default void setPipExclusionBoundsChangeListener(Consumer<Rect> listener) { } 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.MediaController;
import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.UserHandle; import android.os.UserHandle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -74,6 +75,7 @@ public class PipMediaController {
} }
private final Context mContext; private final Context mContext;
private final Handler mMainHandler;
private final MediaSessionManager mMediaSessionManager; private final MediaSessionManager mMediaSessionManager;
private MediaController mMediaController; private MediaController mMediaController;
@@ -118,15 +120,16 @@ public class PipMediaController {
private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); private final ArrayList<ActionListener> mActionListeners = new ArrayList<>();
private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>();
public PipMediaController(Context context) { public PipMediaController(Context context, Handler mainHandler) {
mContext = context; mContext = context;
mMainHandler = mainHandler;
IntentFilter mediaControlFilter = new IntentFilter(); IntentFilter mediaControlFilter = new IntentFilter();
mediaControlFilter.addAction(ACTION_PLAY); mediaControlFilter.addAction(ACTION_PLAY);
mediaControlFilter.addAction(ACTION_PAUSE); mediaControlFilter.addAction(ACTION_PAUSE);
mediaControlFilter.addAction(ACTION_NEXT); mediaControlFilter.addAction(ACTION_NEXT);
mediaControlFilter.addAction(ACTION_PREV); mediaControlFilter.addAction(ACTION_PREV);
mContext.registerReceiver(mPlayPauseActionReceiver, mediaControlFilter, mContext.registerReceiverForAllUsers(mPlayPauseActionReceiver, mediaControlFilter,
UserHandle.USER_ALL); null /* permission */, mainHandler);
createMediaActions(); createMediaActions();
mMediaSessionManager = context.getSystemService(MediaSessionManager.class); mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
@@ -245,7 +248,7 @@ public class PipMediaController {
public void registerSessionListenerForCurrentUser() { public void registerSessionListenerForCurrentUser() {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener); mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null, mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
UserHandle.CURRENT, null); UserHandle.CURRENT, mMainHandler);
} }
/** /**
@@ -277,7 +280,7 @@ public class PipMediaController {
} }
mMediaController = controller; mMediaController = controller;
if (controller != null) { if (controller != null) {
controller.registerCallback(mPlaybackChangedListener); controller.registerCallback(mPlaybackChangedListener, mMainHandler);
} }
notifyActionsChanged(); notifyActionsChanged();
notifyMetadataChanged(getMediaMetadata()); notifyMetadataChanged(getMediaMetadata());

View File

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

View File

@@ -30,6 +30,7 @@ import android.graphics.Matrix;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.RectF; import android.graphics.RectF;
import android.os.Debug; import android.os.Debug;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
@@ -39,6 +40,7 @@ import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SystemWindows; import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener; 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 RectF mTmpDestinationRectF = new RectF();
private final Context mContext; private final Context mContext;
private final PipMediaController mMediaController; private final PipMediaController mMediaController;
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final ArrayList<Listener> mListeners = new ArrayList<>(); private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows; private final SystemWindows mSystemWindows;
@@ -116,11 +120,14 @@ public class PhonePipMenuController implements PipMenuController {
} }
}; };
public PhonePipMenuController(Context context, public PhonePipMenuController(Context context, PipMediaController mediaController,
PipMediaController mediaController, SystemWindows systemWindows) { SystemWindows systemWindows, ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context; mContext = context;
mMediaController = mediaController; mMediaController = mediaController;
mSystemWindows = systemWindows; mSystemWindows = systemWindows;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
} }
public boolean isMenuVisible() { public boolean isMenuVisible() {
@@ -156,7 +163,7 @@ public class PhonePipMenuController implements PipMenuController {
if (mPipMenuView != null) { if (mPipMenuView != null) {
detachPipMenuView(); detachPipMenuView();
} }
mPipMenuView = new PipMenuView(mContext, this); mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler);
mSystemWindows.addView(mPipMenuView, mSystemWindows.addView(mPipMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP); 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;
import android.app.AppOpsManager.OnOpChangedListener; import android.app.AppOpsManager.OnOpChangedListener;
import android.app.IActivityManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Handler;
import android.util.Pair; import android.util.Pair;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.pip.PipUtils;
public class PipAppOpsListener { public class PipAppOpsListener {
private static final String TAG = PipAppOpsListener.class.getSimpleName(); private static final String TAG = PipAppOpsListener.class.getSimpleName();
private Context mContext; private Context mContext;
private Handler mHandler; private ShellExecutor mMainExecutor;
private IActivityManager mActivityManager;
private AppOpsManager mAppOpsManager; private AppOpsManager mAppOpsManager;
private Callback mCallback; private Callback mCallback;
@@ -53,7 +51,7 @@ public class PipAppOpsListener {
if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) && if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
packageName) != MODE_ALLOWED) { packageName) != MODE_ALLOWED) {
mHandler.post(() -> mCallback.dismissPip()); mMainExecutor.execute(() -> mCallback.dismissPip());
} }
} }
} catch (NameNotFoundException e) { } catch (NameNotFoundException e) {
@@ -63,11 +61,9 @@ public class PipAppOpsListener {
} }
}; };
public PipAppOpsListener(Context context, IActivityManager activityManager, public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
Callback callback) {
mContext = context; mContext = context;
mHandler = new Handler(mContext.getMainLooper()); mMainExecutor = mainExecutor;
mActivityManager = activityManager;
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mCallback = callback; mCallback = callback;
} }

View File

@@ -71,11 +71,11 @@ import java.util.function.Consumer;
/** /**
* Manages the picture-in-picture (PIP) UI and states for Phones. * 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 static final String TAG = "PipController";
private Context mContext; private Context mContext;
private ShellExecutor mMainExecutor; protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController; private DisplayController mDisplayController;
private PipInputConsumer mPipInputConsumer; private PipInputConsumer mPipInputConsumer;
private WindowManagerShellWrapper mWindowManagerShellWrapper; private WindowManagerShellWrapper mWindowManagerShellWrapper;
@@ -84,6 +84,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
private PipBoundsAlgorithm mPipBoundsAlgorithm; private PipBoundsAlgorithm mPipBoundsAlgorithm;
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private PipTouchHandler mTouchHandler; private PipTouchHandler mTouchHandler;
protected final PipImpl mImpl = new PipImpl();
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private final Rect mTmpInsetBounds = new Rect(); 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, protected PipController(Context context,
DisplayController displayController, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipAppOpsListener pipAppOpsListener,
@@ -235,7 +258,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mTouchHandler = pipTouchHandler; mTouchHandler = pipTouchHandler;
mAppOpsListener = pipAppOpsListener; mAppOpsListener = pipAppOpsListener;
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP); INPUT_CONSUMER_PIP, mainExecutor);
mPipTaskOrganizer.registerPipTransitionCallback(this); mPipTaskOrganizer.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> { mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
final DisplayInfo newDisplayInfo = new DisplayInfo(); final DisplayInfo newDisplayInfo = new DisplayInfo();
@@ -288,7 +311,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
if (taskInfo != null) { if (taskInfo != null) {
// If SystemUI restart, and it already existed a pinned stack, // If SystemUI restart, and it already existed a pinned stack,
// register the pip input consumer to ensure touch can send to it. // register the pip input consumer to ensure touch can send to it.
mPipInputConsumer.registerInputConsumer(true /* withSfVsync */); mPipInputConsumer.registerInputConsumer();
} }
} catch (RemoteException | UnsupportedOperationException e) { } catch (RemoteException | UnsupportedOperationException e) {
Log.e(TAG, "Failed to register pinned stack listener", e); Log.e(TAG, "Failed to register pinned stack listener", e);
@@ -301,12 +324,10 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
@Override @Override
public void onActivityPinned(String packageName, int userId, int taskId, public void onActivityPinned(String packageName, int userId, int taskId,
int stackId) { int stackId) {
mMainExecutor.execute(() -> { mTouchHandler.onActivityPinned();
mTouchHandler.onActivityPinned(); mMediaController.onActivityPinned();
mMediaController.onActivityPinned(); mAppOpsListener.onActivityPinned(packageName);
mAppOpsListener.onActivityPinned(packageName); mPipInputConsumer.registerInputConsumer();
});
mPipInputConsumer.registerInputConsumer(true /* withSfVsync */);
} }
@Override @Override
@@ -314,10 +335,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
final Pair<ComponentName, Integer> topPipActivityInfo = final Pair<ComponentName, Integer> topPipActivityInfo =
PipUtils.getTopPipActivity(mContext); PipUtils.getTopPipActivity(mContext);
final ComponentName topActivity = topPipActivityInfo.first; final ComponentName topActivity = topPipActivityInfo.first;
mMainExecutor.execute(() -> { mTouchHandler.onActivityUnpinned(topActivity);
mTouchHandler.onActivityUnpinned(topActivity); mAppOpsListener.onActivityUnpinned();
mAppOpsListener.onActivityUnpinned();
});
mPipInputConsumer.unregisterInputConsumer(); mPipInputConsumer.unregisterInputConsumer();
} }
@@ -333,60 +352,46 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
}); });
} }
@Override private void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(Configuration newConfig) { mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mMainExecutor.execute(() -> { mTouchHandler.onConfigurationChanged();
mPipBoundsAlgorithm.onConfigurationChanged(mContext); mPipBoundsState.onConfigurationChanged();
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
});
} }
@Override private void onDensityOrFontScaleChanged() {
public void onDensityOrFontScaleChanged() { mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
mMainExecutor.execute(() -> {
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
});
} }
@Override private void onOverlayChanged() {
public void onOverlayChanged() { mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay()));
mMainExecutor.execute(() -> { updateMovementBounds(null /* toBounds */,
mPipBoundsState.setDisplayLayout(new DisplayLayout(mContext, mContext.getDisplay())); false /* fromRotation */, false /* fromImeAdjustment */,
updateMovementBounds(null /* toBounds */, false /* fromShelfAdjustment */,
false /* fromRotation */, false /* fromImeAdjustment */, null /* windowContainerTransaction */);
false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
});
} }
@Override private void registerSessionListenerForCurrentUser() {
public void registerSessionListenerForCurrentUser() {
mMediaController.registerSessionListenerForCurrentUser(); mMediaController.registerSessionListenerForCurrentUser();
} }
@Override private void onSystemUiStateChanged(boolean isValidState, int flag) {
public void onSystemUiStateChanged(boolean isValidState, int flag) {
mTouchHandler.onSystemUiStateChanged(isValidState); mTouchHandler.onSystemUiStateChanged(isValidState);
} }
/** /**
* Expands the PIP. * Expands the PIP.
*/ */
@Override
public void expandPip() { public void expandPip() {
mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */); mTouchHandler.getMotionHelper().expandLeavePip(false /* skipAnimation */);
} }
@Override private PipTouchHandler getPipTouchHandler() {
public PipTouchHandler getPipTouchHandler() {
return mTouchHandler; return mTouchHandler;
} }
/** /**
* Hides the PIP menu. * Hides the PIP menu.
*/ */
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) { public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
mMenuController.hideMenu(onStartCallback, onEndCallback); mMenuController.hideMenu(onStartCallback, onEndCallback);
} }
@@ -408,9 +413,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
/** /**
* Sets both shelf visibility and its height. * Sets both shelf visibility and its height.
*/ */
@Override private void setShelfHeight(boolean visible, int height) {
public void setShelfHeight(boolean visible, int height) { setShelfHeightLocked(visible, height);
mMainExecutor.execute(() -> setShelfHeightLocked(visible, height));
} }
private void setShelfHeightLocked(boolean visible, int height) { private void setShelfHeightLocked(boolean visible, int height) {
@@ -418,18 +422,15 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipBoundsState.setShelfVisibility(visible, shelfHeight); mPipBoundsState.setShelfVisibility(visible, shelfHeight);
} }
@Override private void setPinnedStackAnimationType(int animationType) {
public void setPinnedStackAnimationType(int animationType) { mPipTaskOrganizer.setOneShotAnimationType(animationType);
mMainExecutor.execute(() -> mPipTaskOrganizer.setOneShotAnimationType(animationType));
} }
@Override private void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
public void setPinnedStackAnimationListener(Consumer<Boolean> callback) { mPinnedStackAnimationRecentsCallback = callback;
mMainExecutor.execute(() -> mPinnedStackAnimationRecentsCallback = callback);
} }
@Override private Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams, PictureInPictureParams pictureInPictureParams,
int launcherRotation, int shelfHeight) { int launcherRotation, int shelfHeight) {
setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight); setShelfHeightLocked(shelfHeight > 0 /* visible */, shelfHeight);
@@ -438,11 +439,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
pictureInPictureParams); pictureInPictureParams);
} }
@Override private void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
mPipTaskOrganizer.stopSwipePipToHome(componentName, 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 @Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) { public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
if (isOutPipDirection(direction)) { 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 @Override
public void onPipTransitionFinished(ComponentName activity, int direction) { public void onPipTransitionFinished(ComponentName activity, int direction) {
onPipTransitionFinishedOrCanceled(direction); onPipTransitionFinishedOrCanceled(direction);
@@ -607,8 +606,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
} }
} }
@Override private void dump(PrintWriter pw) {
public void dump(PrintWriter pw) {
final String innerPrefix = " "; final String innerPrefix = " ";
pw.println(TAG); pw.println(TAG);
mMenuController.dump(pw, innerPrefix); mMenuController.dump(pw, innerPrefix);
@@ -619,23 +617,123 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
mPipInputConsumer.dump(pw, innerPrefix); mPipInputConsumer.dump(pw, innerPrefix);
} }
/** private class PipImpl implements Pip {
* Instantiates {@link PipController}, returns {@code null} if the feature not supported. @Override
*/ public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
@Nullable mMainExecutor.execute(() -> {
public static PipController create(Context context, DisplayController displayController, PipController.this.hidePipMenu(onStartCallback, onEndCallback);
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, @Override
pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer, public void expandPip() {
pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor); 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.R;
import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.DismissCircleView; 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.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.PipUiEventLogger;
import java.util.concurrent.TimeUnit;
import kotlin.Unit; import kotlin.Unit;
/** /**
@@ -57,36 +60,32 @@ public class PipDismissTargetHandler {
* MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
* PIP. * 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 * 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. * 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. */ /** 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. * 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. */ /** Default configuration to use for springing the dismiss target in/out. */
private final PhysicsAnimator.SpringConfig mTargetSpringConfig = private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
new PhysicsAnimator.SpringConfig( new PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); 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 // Allow dragging the PIP to a location to close it
private final boolean mEnableDismissDragToEdge; private final boolean mEnableDismissDragToEdge;
@@ -96,74 +95,76 @@ public class PipDismissTargetHandler {
private final PipMotionHelper mMotionHelper; private final PipMotionHelper mMotionHelper;
private final PipUiEventLogger mPipUiEventLogger; private final PipUiEventLogger mPipUiEventLogger;
private final WindowManager mWindowManager; private final WindowManager mWindowManager;
private final Handler mHandler; private final ShellExecutor mMainExecutor;
public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger, public PipDismissTargetHandler(Context context, PipUiEventLogger pipUiEventLogger,
PipMotionHelper motionHelper, Handler handler) { PipMotionHelper motionHelper, ShellExecutor mainExecutor) {
mContext = context; mContext = context;
mPipUiEventLogger = pipUiEventLogger; mPipUiEventLogger = pipUiEventLogger;
mMotionHelper = motionHelper; mMotionHelper = motionHelper;
mHandler = handler; mMainExecutor = mainExecutor;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Resources res = context.getResources(); Resources res = context.getResources();
mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge); mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height); mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
mTargetView = new DismissCircleView(context); mMainExecutor.execute(() -> {
mTargetViewContainer = new FrameLayout(context); mTargetView = new DismissCircleView(context);
mTargetViewContainer.setBackgroundDrawable( mTargetViewContainer = new FrameLayout(context);
context.getDrawable(R.drawable.floating_dismiss_gradient_transition)); mTargetViewContainer.setBackgroundDrawable(
mTargetViewContainer.setClipChildren(false); context.getDrawable(R.drawable.floating_dismiss_gradient_transition));
mTargetViewContainer.addView(mTargetView); mTargetViewContainer.setClipChildren(false);
mTargetViewContainer.addView(mTargetView);
mMagnetizedPip = mMotionHelper.getMagnetizedPip(); mMagnetizedPip = mMotionHelper.getMagnetizedPip();
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
updateMagneticTargetSize(); updateMagneticTargetSize();
mMagnetizedPip.setAnimateStuckToTarget( mMagnetizedPip.setAnimateStuckToTarget(
(target, velX, velY, flung, after) -> { (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) { 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 @Override
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
float velX, float velY, boolean wasFlungOut) { float velX, float velY, boolean wasFlungOut) {
if (wasFlungOut) { if (wasFlungOut) {
mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */); mMotionHelper.flingToSnapTarget(velX, velY, null /* endAction */);
hideDismissTargetMaybe(); hideDismissTargetMaybe();
} else { } else {
mMotionHelper.setSpringingToTouch(true); mMotionHelper.setSpringingToTouch(true);
}
} }
}
@Override @Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mMotionHelper.notifyDismissalPending(); mMainExecutor.executeDelayed(() -> {
mMotionHelper.notifyDismissalPending();
mMotionHelper.animateDismiss();
hideDismissTargetMaybe();
handler.post(() -> { mPipUiEventLogger.log(
mMotionHelper.animateDismiss(); PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
hideDismissTargetMaybe(); }, 0);
}); }
});
mPipUiEventLogger.log( mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_DRAG_TO_REMOVE);
}
}); });
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. */ /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
public void createOrUpdateDismissTarget() { public void createOrUpdateDismissTarget() {
if (!mTargetViewContainer.isAttachedToWindow()) { if (!mTargetViewContainer.isAttachedToWindow()) {
mHandler.removeCallbacks(mShowTargetAction);
mMagneticTargetAnimator.cancel(); mMagneticTargetAnimator.cancel();
mTargetViewContainer.setVisibility(View.INVISIBLE); mTargetViewContainer.setVisibility(View.INVISIBLE);
@@ -270,7 +270,6 @@ public class PipDismissTargetHandler {
return; return;
} }
mHandler.removeCallbacks(mShowTargetAction);
mMagneticTargetAnimator mMagneticTargetAnimator
.spring(DynamicAnimation.TRANSLATION_Y, .spring(DynamicAnimation.TRANSLATION_Y,
mTargetViewContainer.getHeight(), mTargetViewContainer.getHeight(),
@@ -286,8 +285,6 @@ public class PipDismissTargetHandler {
* Removes the dismiss target and cancels any pending callbacks to show it. * Removes the dismiss target and cancels any pending callbacks to show it.
*/ */
public void cleanUpDismissTarget() { public void cleanUpDismissTarget() {
mHandler.removeCallbacks(mShowTargetAction);
if (mTargetViewContainer.isAttachedToWindow()) { if (mTargetViewContainer.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(mTargetViewContainer); mWindowManager.removeViewImmediate(mTargetViewContainer);
} }

View File

@@ -29,6 +29,8 @@ import android.view.IWindowManager;
import android.view.InputChannel; import android.view.InputChannel;
import android.view.InputEvent; import android.view.InputEvent;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter; import java.io.PrintWriter;
/** /**
@@ -81,6 +83,7 @@ public class PipInputConsumer {
private final IWindowManager mWindowManager; private final IWindowManager mWindowManager;
private final IBinder mToken; private final IBinder mToken;
private final String mName; private final String mName;
private final ShellExecutor mMainExecutor;
private InputEventReceiver mInputEventReceiver; private InputEventReceiver mInputEventReceiver;
private InputListener mListener; 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. * @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; mWindowManager = windowManager;
mToken = new Binder(); mToken = new Binder();
mName = name; mName = name;
mMainExecutor = mainExecutor;
} }
/** /**
@@ -107,9 +112,11 @@ public class PipInputConsumer {
*/ */
public void setRegistrationListener(RegistrationListener listener) { public void setRegistrationListener(RegistrationListener listener) {
mRegistrationListener = listener; mRegistrationListener = listener;
if (mRegistrationListener != null) { mMainExecutor.execute(() -> {
mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); if (mRegistrationListener != null) {
} mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null);
}
});
} }
/** /**
@@ -125,14 +132,6 @@ public class PipInputConsumer {
* Registers the input consumer. * Registers the input consumer.
*/ */
public void registerInputConsumer() { 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) { if (mInputEventReceiver != null) {
return; return;
} }
@@ -144,11 +143,15 @@ public class PipInputConsumer {
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Failed to create input consumer", e); Log.e(TAG, "Failed to create input consumer", e);
} }
mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(), mMainExecutor.execute(() -> {
withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance()); // Choreographer.getSfInstance() must be called on the thread that the input event
if (mRegistrationListener != null) { // receiver should be receiving events
mRegistrationListener.onRegistrationChanged(true /* isRegistered */); 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.dispose();
mInputEventReceiver = null; mInputEventReceiver = null;
if (mRegistrationListener != null) { mMainExecutor.execute(() -> {
mRegistrationListener.onRegistrationChanged(false /* isRegistered */); if (mRegistrationListener != null) {
} mRegistrationListener.onRegistrationChanged(false /* isRegistered */);
}
});
} }
public void dump(PrintWriter pw, String prefix) { 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.R;
import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.pip.PipUtils;
import java.util.ArrayList; 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; private final Runnable mHideMenuRunnable = this::hideMenu;
@@ -127,10 +129,13 @@ public class PipMenuView extends FrameLayout {
protected View mTopEndContainer; protected View mTopEndContainer;
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
public PipMenuView(Context context, PhonePipMenuController controller) { public PipMenuView(Context context, PhonePipMenuController controller,
ShellExecutor mainExecutor, Handler mainHandler) {
super(context, null, 0); super(context, null, 0);
mContext = context; mContext = context;
mController = controller; mController = controller;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
inflate(context, R.layout.pip_menu, this); inflate(context, R.layout.pip_menu, this);
@@ -412,17 +417,15 @@ public class PipMenuView extends FrameLayout {
d.setTint(Color.WHITE); d.setTint(Color.WHITE);
actionView.setImageDrawable(d); actionView.setImageDrawable(d);
} }
}, mHandler); }, mMainHandler);
actionView.setContentDescription(action.getContentDescription()); actionView.setContentDescription(action.getContentDescription());
if (action.isEnabled()) { if (action.isEnabled()) {
actionView.setOnClickListener(v -> { actionView.setOnClickListener(v -> {
mHandler.post(() -> { try {
try { action.getActionIntent().send();
action.getActionIntent().send(); } catch (CanceledException e) {
} catch (CanceledException e) { Log.w(TAG, "Failed to send action", e);
Log.w(TAG, "Failed to send action", e); }
}
});
}); });
} }
actionView.setEnabled(action.isEnabled()); actionView.setEnabled(action.isEnabled());
@@ -480,13 +483,13 @@ public class PipMenuView extends FrameLayout {
} }
private void cancelDelayedHide() { private void cancelDelayedHide() {
mHandler.removeCallbacks(mHideMenuRunnable); mMainExecutor.removeCallbacks(mHideMenuRunnable);
} }
private void repostDelayedHide(int delay) { private void repostDelayedHide(int delay) {
int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay, int recommendedTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(delay,
FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS); FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS);
mHandler.removeCallbacks(mHideMenuRunnable); mMainExecutor.removeCallbacks(mHideMenuRunnable);
mHandler.postDelayed(mHideMenuRunnable, recommendedTimeout); 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.FloatProperties;
import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.FloatingContentCoordinator; 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.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipSnapAlgorithm;
@@ -74,8 +75,6 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private PhonePipMenuController mMenuController; private PhonePipMenuController mMenuController;
private PipSnapAlgorithm mSnapAlgorithm; private PipSnapAlgorithm mSnapAlgorithm;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
/** The region that all of PIP must stay within. */ /** The region that all of PIP must stay within. */
private final Rect mFloatingAllowedArea = new Rect(); private final Rect mFloatingAllowedArea = new Rect();
@@ -130,10 +129,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> { private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
mMainHandler.post(() -> { mMenuController.updateMenuLayout(newBounds);
mMenuController.updateMenuLayout(newBounds); mPipBoundsState.setBounds(newBounds);
mPipBoundsState.setBounds(newBounds);
});
}; };
/** /**
@@ -174,7 +171,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState, public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator) { PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator,
ShellExecutor mainExecutor) {
mContext = context; mContext = context;
mPipTaskOrganizer = pipTaskOrganizer; mPipTaskOrganizer = pipTaskOrganizer;
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
@@ -184,8 +182,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback);
mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance(
mPipBoundsState.getMotionBoundsState().getBoundsInMotion()); 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) -> { mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) { if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
@@ -256,10 +258,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds); mPipBoundsState.getMotionBoundsState().setBoundsInMotion(toBounds);
mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds, mPipTaskOrganizer.scheduleUserResizePip(getBounds(), toBounds,
(Rect newBounds) -> { (Rect newBounds) -> {
mMainHandler.post(() -> {
mMenuController.updateMenuLayout(newBounds); mMenuController.updateMenuLayout(newBounds);
}); });
});
} }
} else { } else {
// If PIP is 'catching up' after being stuck in the dismiss target, update the animation // 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(); cancelPhysicsAnimation();
mMenuController.hideMenuWithoutResize(); 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_WIDTH, getBounds().width(), mSpringConfig)
.spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig) .spring(FloatProperties.RECT_HEIGHT, getBounds().height(), mSpringConfig)
.flingThenSpring( .flingThenSpring(
FloatProperties.RECT_X, velocityX, isStash ? mStashConfigX : mFlingConfigX, FloatProperties.RECT_X, velocityX,
isStash ? mStashConfigX : mFlingConfigX,
mSpringConfig, true /* flingMustReachMinOrMax */) mSpringConfig, true /* flingMustReachMinOrMax */)
.flingThenSpring( .flingThenSpring(
FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig); FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig);

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ import android.view.VelocityTracker;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.ShellExecutor;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -39,7 +40,7 @@ public class PipTouchState {
public static final long DOUBLE_TAP_TIMEOUT = 200; public static final long DOUBLE_TAP_TIMEOUT = 200;
static final long HOVER_EXIT_TIMEOUT = 50; static final long HOVER_EXIT_TIMEOUT = 50;
private final Handler mHandler; private final ShellExecutor mMainExecutor;
private final ViewConfiguration mViewConfig; private final ViewConfiguration mViewConfig;
private final Runnable mDoubleTapTimeoutCallback; private final Runnable mDoubleTapTimeoutCallback;
private final Runnable mHoverExitTimeoutCallback; private final Runnable mHoverExitTimeoutCallback;
@@ -67,12 +68,12 @@ public class PipTouchState {
private int mActivePointerId; private int mActivePointerId;
private int mLastTouchDisplayId = Display.INVALID_DISPLAY; private int mLastTouchDisplayId = Display.INVALID_DISPLAY;
public PipTouchState(ViewConfiguration viewConfig, Handler handler, public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback,
Runnable doubleTapTimeoutCallback, Runnable hoverExitTimeoutCallback) { Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) {
mViewConfig = viewConfig; mViewConfig = viewConfig;
mHandler = handler;
mDoubleTapTimeoutCallback = doubleTapTimeoutCallback; mDoubleTapTimeoutCallback = doubleTapTimeoutCallback;
mHoverExitTimeoutCallback = hoverExitTimeoutCallback; mHoverExitTimeoutCallback = hoverExitTimeoutCallback;
mMainExecutor = mainExecutor;
} }
/** /**
@@ -116,7 +117,7 @@ public class PipTouchState {
mIsDragging = false; mIsDragging = false;
mLastDownTouchTime = mDownTouchTime; mLastDownTouchTime = mDownTouchTime;
if (mDoubleTapTimeoutCallback != null) { if (mDoubleTapTimeoutCallback != null) {
mHandler.removeCallbacks(mDoubleTapTimeoutCallback); mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
} }
break; break;
} }
@@ -324,8 +325,8 @@ public class PipTouchState {
public void scheduleDoubleTapTimeoutCallback() { public void scheduleDoubleTapTimeoutCallback() {
if (mIsWaitingForDoubleTap) { if (mIsWaitingForDoubleTap) {
long delay = getDoubleTapTimeoutCallbackDelay(); long delay = getDoubleTapTimeoutCallbackDelay();
mHandler.removeCallbacks(mDoubleTapTimeoutCallback); mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
mHandler.postDelayed(mDoubleTapTimeoutCallback, delay); mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay);
} }
} }
@@ -342,17 +343,17 @@ public class PipTouchState {
*/ */
public void removeDoubleTapTimeoutCallback() { public void removeDoubleTapTimeoutCallback() {
mIsWaitingForDoubleTap = false; mIsWaitingForDoubleTap = false;
mHandler.removeCallbacks(mDoubleTapTimeoutCallback); mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback);
} }
@VisibleForTesting @VisibleForTesting
public void scheduleHoverExitTimeoutCallback() { public void scheduleHoverExitTimeoutCallback() {
mHandler.removeCallbacks(mHoverExitTimeoutCallback); mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
mHandler.postDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT); mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT);
} }
void removeHoverExitTimeoutCallback() { void removeHoverExitTimeoutCallback() {
mHandler.removeCallbacks(mHoverExitTimeoutCallback); mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback);
} }
void addMovementToVelocityTracker(MotionEvent event) { 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.R;
import com.android.wm.shell.WindowManagerShellWrapper; 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.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder; 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. * 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 { TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
private static final String TAG = "TvPipController"; private static final String TAG = "TvPipController";
static final boolean DEBUG = true; static final boolean DEBUG = true;
@@ -90,13 +91,15 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
private final PipMediaController mPipMediaController; private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController; private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController; private final TvPipMenuController mTvPipMenuController;
private final ShellExecutor mMainExecutor;
private final TvPipImpl mImpl = new TvPipImpl();
private @State int mState = STATE_NO_PIP; private @State int mState = STATE_NO_PIP;
private int mPinnedTaskId = NONEXISTENT_TASK_ID; private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private int mResizeAnimationDuration; private int mResizeAnimationDuration;
public TvPipController( public static Pip create(
Context context, Context context,
PipBoundsState pipBoundsState, PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm, PipBoundsAlgorithm pipBoundsAlgorithm,
@@ -105,8 +108,34 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
PipMediaController pipMediaController, PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController, TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener, 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; mContext = context;
mMainExecutor = mainExecutor;
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
mPipBoundsState.setDisplayInfo(getDisplayInfo()); mPipBoundsState.setDisplayInfo(getDisplayInfo());
@@ -129,8 +158,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
registerWmShellPinnedStackListener(wmShell); registerWmShellPinnedStackListener(wmShell);
} }
@Override private void onConfigurationChanged(Configuration newConfig) {
public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState)); if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
if (isPipShown()) { if (isPipShown()) {
@@ -145,8 +173,7 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
/** /**
* Returns {@code true} if Pip is shown. * Returns {@code true} if Pip is shown.
*/ */
@Override private boolean isPipShown() {
public boolean isPipShown() {
return mState != STATE_NO_PIP; 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 * @param state the to determine the Pip bounds. IMPORTANT: should always match the current
* state of the Controller. * state of the Controller.
*/ */
@Override private void resizePinnedStack(@State int state) {
public void resizePinnedStack(@State int state) {
if (state != mState) { if (state != mState) {
throw new IllegalArgumentException("The passed state should match the current state!"); 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); mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
} }
@Override private void registerSessionListenerForCurrentUser() {
public void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser(); mPipMediaController.registerSessionListenerForCurrentUser();
} }
@@ -418,4 +443,20 @@ public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallb
throw new IllegalArgumentException("Unknown state " + state); 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.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.SurfaceControl; import android.view.SurfaceControl;
@@ -47,6 +48,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final Context mContext; private final Context mContext;
private final SystemWindows mSystemWindows; private final SystemWindows mSystemWindows;
private final PipBoundsState mPipBoundsState; private final PipBoundsState mPipBoundsState;
private final Handler mMainHandler;
private Delegate mDelegate; private Delegate mDelegate;
private SurfaceControl mLeash; private SurfaceControl mLeash;
@@ -56,10 +58,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final List<RemoteAction> mAppActions = new ArrayList<>(); private final List<RemoteAction> mAppActions = new ArrayList<>();
public TvPipMenuController(Context context, PipBoundsState pipBoundsState, public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
SystemWindows systemWindows, PipMediaController pipMediaController) { SystemWindows systemWindows, PipMediaController pipMediaController,
Handler mainHandler) {
mContext = context; mContext = context;
mPipBoundsState = pipBoundsState; mPipBoundsState = pipBoundsState;
mSystemWindows = systemWindows; mSystemWindows = systemWindows;
mMainHandler = mainHandler;
// We need to "close" the menu the platform call for all the system dialogs to close (for // We need to "close" the menu the platform call for all the system dialogs to close (for
// example, on the Home button press). // example, on the Home button press).
@@ -69,8 +73,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
hideMenu(); hideMenu();
} }
}; };
context.registerReceiver(closeSystemDialogsBroadcastReceiver, context.registerReceiverForAllUsers(closeSystemDialogsBroadcastReceiver,
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), null /* permission */,
mainHandler);
pipMediaController.addActionListener(this::onMediaActionsChanged); pipMediaController.addActionListener(this::onMediaActionsChanged);
} }
@@ -199,9 +204,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return; return;
} }
if (!mAppActions.isEmpty()) { if (!mAppActions.isEmpty()) {
mMenuView.setAdditionalActions(mAppActions); mMenuView.setAdditionalActions(mAppActions, mMainHandler);
} else { } 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 * 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 * 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 { public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView"; 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 static final float DISABLED_ACTION_ALPHA = 0.54f;
private final Handler mUiThreadHandler;
private final Animator mFadeInAnimation; private final Animator mFadeInAnimation;
private final Animator mFadeOutAnimation; private final Animator mFadeOutAnimation;
@Nullable private Listener mListener; @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, public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
int defStyleRes) { int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
mUiThreadHandler = new Handler(Looper.getMainLooper());
inflate(context, R.layout.tv_pip_menu, this); 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()"); if (DEBUG) Log.d(TAG, "setAdditionalActions()");
// Make sure we exactly as many additional buttons as we have actions to display. // 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 -> { action.getIcon().loadDrawableAsync(mContext, drawable -> {
drawable.setTint(Color.WHITE); drawable.setTint(Color.WHITE);
button.setImageDrawable(drawable); button.setImageDrawable(drawable);
}, mUiThreadHandler); }, mainHandler);
} }
} }

View File

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

View File

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

View File

@@ -43,7 +43,9 @@ import android.window.WindowContainerToken;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase; 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.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.phone.PhonePipMenuController; import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
@@ -71,6 +73,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Mock private PipUiEventLogger mMockPipUiEventLogger; @Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen; @Mock private Optional<LegacySplitScreen> mMockOptionalSplitScreen;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer; @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
private TestShellExecutor mMainExecutor;
private PipBoundsState mPipBoundsState; private PipBoundsState mPipBoundsState;
private ComponentName mComponent1; private ComponentName mComponent1;
@@ -82,10 +85,12 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mComponent1 = new ComponentName(mContext, "component1"); mComponent1 = new ComponentName(mContext, "component1");
mComponent2 = new ComponentName(mContext, "component2"); mComponent2 = new ComponentName(mContext, "component2");
mPipBoundsState = new PipBoundsState(mContext); mPipBoundsState = new PipBoundsState(mContext);
mMainExecutor = new TestShellExecutor();
mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState, mSpiedPipTaskOrganizer = spy(new PipTaskOrganizer(mContext, mPipBoundsState,
mMockPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipBoundsAlgorithm, mMockPhonePipMenuController,
mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController, mMockPipSurfaceTransactionHelper, mMockOptionalSplitScreen, mMockdDisplayController,
mMockPipUiEventLogger, mMockShellTaskOrganizer)); mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor));
mMainExecutor.flushAll();
preparePipTaskOrg(); 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.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock; import android.os.SystemClock;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ViewConfiguration; import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -45,23 +42,22 @@ import java.util.concurrent.CountDownLatch;
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@SmallTest @SmallTest
@RunWithLooper
public class PipTouchStateTest extends ShellTestCase { public class PipTouchStateTest extends ShellTestCase {
private PipTouchState mTouchState; private PipTouchState mTouchState;
private CountDownLatch mDoubleTapCallbackTriggeredLatch; private CountDownLatch mDoubleTapCallbackTriggeredLatch;
private CountDownLatch mHoverExitCallbackTriggeredLatch; private CountDownLatch mHoverExitCallbackTriggeredLatch;
private TestShellExecutor mShellMainExecutor;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mShellMainExecutor = new TestShellExecutor();
mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1); mDoubleTapCallbackTriggeredLatch = new CountDownLatch(1);
mHoverExitCallbackTriggeredLatch = new CountDownLatch(1); mHoverExitCallbackTriggeredLatch = new CountDownLatch(1);
mTouchState = new PipTouchState(ViewConfiguration.get(getContext()), mTouchState = new PipTouchState(ViewConfiguration.get(getContext()),
Handler.createAsync(Looper.myLooper()), () -> { mDoubleTapCallbackTriggeredLatch::countDown,
mDoubleTapCallbackTriggeredLatch.countDown(); mHoverExitCallbackTriggeredLatch::countDown,
}, () -> { mShellMainExecutor);
mHoverExitCallbackTriggeredLatch.countDown();
});
assertFalse(mTouchState.isDoubleTap()); assertFalse(mTouchState.isDoubleTap());
assertFalse(mTouchState.isWaitingForDoubleTap()); assertFalse(mTouchState.isWaitingForDoubleTap());
} }
@@ -91,9 +87,7 @@ public class PipTouchStateTest extends ShellTestCase {
assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10); assertTrue(mTouchState.getDoubleTapTimeoutCallbackDelay() == 10);
mTouchState.scheduleDoubleTapTimeoutCallback(); mTouchState.scheduleDoubleTapTimeoutCallback();
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit. mShellMainExecutor.flushAll();
Thread.sleep(15);
TestableLooper.get(this).processAllMessages();
assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0); assertTrue(mDoubleTapCallbackTriggeredLatch.getCount() == 0);
} }
@@ -128,17 +122,13 @@ public class PipTouchStateTest extends ShellTestCase {
@Test @Test
public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception { public void testHoverExitTimeout_timeoutCallbackCalled() throws Exception {
mTouchState.scheduleHoverExitTimeoutCallback(); mTouchState.scheduleHoverExitTimeoutCallback();
mShellMainExecutor.flushAll();
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
Thread.sleep(50);
TestableLooper.get(this).processAllMessages();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 0);
} }
@Test @Test
public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception { public void testHoverExitTimeout_timeoutCallbackNotCalled() throws Exception {
mTouchState.scheduleHoverExitTimeoutCallback(); mTouchState.scheduleHoverExitTimeoutCallback();
TestableLooper.get(this).processAllMessages();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
} }
@@ -147,14 +137,12 @@ public class PipTouchStateTest extends ShellTestCase {
mTouchState.scheduleHoverExitTimeoutCallback(); mTouchState.scheduleHoverExitTimeoutCallback();
mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(), mTouchState.onTouchEvent(createMotionEvent(ACTION_BUTTON_PRESS, SystemClock.uptimeMillis(),
0, 0)); 0, 0));
mShellMainExecutor.flushAll();
// TODO: Remove this sleep. Its only being added because it speeds up this test a bit.
Thread.sleep(50);
TestableLooper.get(this).processAllMessages();
assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1); assertTrue(mHoverExitCallbackTriggeredLatch.getCount() == 1);
} }
private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) { private MotionEvent createMotionEvent(int action, long eventTime, float x, float y) {
return MotionEvent.obtain(0, eventTime, action, x, y, 0); return MotionEvent.obtain(0, eventTime, action, x, y, 0);
} }
} }

View File

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

View File

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

View File

@@ -19,6 +19,7 @@ package com.android.systemui.wmshell;
import android.animation.AnimationHandler; import android.animation.AnimationHandler;
import android.app.ActivityTaskManager; import android.app.ActivityTaskManager;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager; import android.view.IWindowManager;
import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.WMSingleton;
@@ -125,11 +126,15 @@ public class WMShellModule {
return new PipBoundsAlgorithm(context, pipBoundsState); return new PipBoundsAlgorithm(context, pipBoundsState);
} }
// Handler is used by Icon.loadDrawableAsync
@WMSingleton @WMSingleton
@Provides @Provides
static PhonePipMenuController providesPipPhoneMenuController(Context context, static PhonePipMenuController providesPipPhoneMenuController(Context context,
PipMediaController pipMediaController, SystemWindows systemWindows) { PipMediaController pipMediaController, SystemWindows systemWindows,
return new PhonePipMenuController(context, pipMediaController, systemWindows); @ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler) {
return new PhonePipMenuController(context, pipMediaController, systemWindows,
mainExecutor, mainHandler);
} }
@WMSingleton @WMSingleton
@@ -154,9 +159,10 @@ public class WMShellModule {
PhonePipMenuController menuPhoneController, PhonePipMenuController menuPhoneController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper, PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController, Optional<LegacySplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) { PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm, return new PipTaskOrganizer(context, pipBoundsState, pipBoundsAlgorithm,
menuPhoneController, pipSurfaceTransactionHelper, splitScreenOptional, 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 ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState; @Mock SysUiState mSysUiState;
@Mock Pip mPip; @Mock Pip mPip;
@Mock PipTouchHandler mPipTouchHandler;
@Mock LegacySplitScreen mLegacySplitScreen; @Mock LegacySplitScreen mLegacySplitScreen;
@Mock OneHanded mOneHanded; @Mock OneHanded mOneHanded;
@Mock HideDisplayCutout mHideDisplayCutout; @Mock HideDisplayCutout mHideDisplayCutout;
@@ -80,8 +79,6 @@ public class WMShellTest extends SysuiTestCase {
Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController, Optional.of(mShellCommandHandler), mCommandQueue, mConfigurationController,
mKeyguardUpdateMonitor, mNavigationModeController, mKeyguardUpdateMonitor, mNavigationModeController,
mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor); mScreenLifecycle, mSysUiState, mProtoTracer, mSysUiMainExecutor);
when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler);
} }
@Test @Test