Fix touch goes to the window behide PiP
When an Acitivty enter PictureInPicture mode, the pip input consumer
would be created and show the PipMenuActivity at first entry
(MENU_STATE_CLOSE) or show full menu (MENU_STATE_FULL) if user clicks
the pip window.
So in order to control the pip wndow dragging and menu, the pip input
consumer would be destoryed when menu window has shown, and it would be
created when the pip window starts to drag or the menu has hidden.
And the menu window should also has the flag FLAG_SLIPPERY to make sure
touch can through to the pip input consumer, but it could not guarantee
which window can receive the touch.
This patch would make sure all touch events except ACTION_OUTSIDE can
go through pip input consumer because it's above the pip window and it
can integrate all behaviors like window dragging or show menu in
PipTouchHandler without doing same thing and adding FLAG_SLIPPERY in
PipMenuActivity.
Test: atest PinnedStackTests
Test: atest SystemUITests
Bug: 139805619
Change-Id: I35f9eba867561168384d0a0d6c0608a6dbdf6a09
(cherry picked from commit d52d11933030ff3abec3735ddda376f0c019d8db)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 1740290..3be3422 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,6 +16,9 @@
package com.android.systemui.pip.phone;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
@@ -182,7 +185,6 @@
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
mInputConsumerController = InputConsumerController.getPipInputConsumer();
- mInputConsumerController.registerInputConsumer();
mMediaController = new PipMediaController(context, mActivityManager);
mMenuController = new PipMenuActivityController(context, mActivityManager, mMediaController,
mInputConsumerController);
@@ -190,6 +192,18 @@
mMenuController, mInputConsumerController);
mAppOpsListener = new PipAppOpsListener(context, mActivityManager,
mTouchHandler.getMotionHelper());
+
+ // If SystemUI restart, and it already existed a pinned stack,
+ // register the pip input consumer to ensure touch can send to it.
+ try {
+ ActivityManager.StackInfo stackInfo = mActivityTaskManager.getStackInfo(
+ WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+ if (stackInfo != null) {
+ mInputConsumerController.registerInputConsumer();
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 86ce60d..ec6d7ff 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -48,7 +48,6 @@
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.Color;
-import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -64,7 +63,6 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityManager;
@@ -92,6 +90,7 @@
public static final int MESSAGE_UPDATE_ACTIONS = 4;
public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5;
public static final int MESSAGE_ANIMATION_ENDED = 6;
+ public static final int MESSAGE_TOUCH_EVENT = 7;
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -129,10 +128,6 @@
}
};
- private PipTouchState mTouchState;
- private PointF mDownPosition = new PointF();
- private PointF mDownDelta = new PointF();
- private ViewConfiguration mViewConfig;
private Handler mHandler = new Handler();
private Messenger mToControllerMessenger;
private Messenger mMessenger = new Messenger(new Handler() {
@@ -170,6 +165,12 @@
mAllowTouches = true;
break;
}
+
+ case MESSAGE_TOUCH_EVENT: {
+ final MotionEvent ev = (MotionEvent) msg.obj;
+ dispatchTouchEvent(ev);
+ break;
+ }
}
}
});
@@ -185,15 +186,7 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
// Set the flags to allow us to watch for outside touches and also hide the menu and start
// manipulating the PIP in the same touch gesture
- mViewConfig = ViewConfiguration.get(this);
- mTouchState = new PipTouchState(mViewConfig, mHandler, () -> {
- if (mMenuState == MENU_STATE_CLOSE) {
- showPipMenu();
- } else {
- expandPip();
- }
- });
- getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);
+ getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
super.onCreate(savedInstanceState);
setContentView(R.layout.pip_menu_activity);
@@ -205,32 +198,6 @@
mViewRoot.setBackground(mBackgroundDrawable);
mMenuContainer = findViewById(R.id.menu_container);
mMenuContainer.setAlpha(0);
- mMenuContainer.setOnTouchListener((v, event) -> {
- mTouchState.onTouchEvent(event);
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
- if (mTouchState.isDoubleTap() || mMenuState == MENU_STATE_FULL) {
- // Expand to fullscreen if this is a double tap or we are already expanded
- expandPip();
- } else if (!mTouchState.isWaitingForDoubleTap()) {
- // User has stalled long enough for this not to be a drag or a double tap,
- // just expand the menu if necessary
- if (mMenuState == MENU_STATE_CLOSE) {
- showPipMenu();
- }
- } else {
- // Next touch event _may_ be the second tap for the double-tap, schedule a
- // fallback runnable to trigger the menu if no touch event occurs before the
- // next tap
- mTouchState.scheduleDoubleTapTimeoutCallback();
- }
- // Fall through
- case MotionEvent.ACTION_CANCEL:
- mTouchState.reset();
- break;
- }
- return true;
- });
mSettingsButton = findViewById(R.id.settings);
mSettingsButton.setAlpha(0);
mSettingsButton.setOnClickListener((v) -> {
@@ -240,8 +207,11 @@
});
mDismissButton = findViewById(R.id.dismiss);
mDismissButton.setAlpha(0);
- mDismissButton.setOnClickListener((v) -> {
- dismissPip();
+ mDismissButton.setOnClickListener(v -> dismissPip());
+ findViewById(R.id.expand_button).setOnClickListener(v -> {
+ if (mMenuContainer.getAlpha() != 0) {
+ expandPip();
+ }
});
mActionsGroup = findViewById(R.id.actions_group);
mBetweenActionPaddingLand = getResources().getDimensionPixelSize(
@@ -301,27 +271,14 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!mAllowTouches) {
- return super.dispatchTouchEvent(ev);
+ return false;
}
// On the first action outside the window, hide the menu
switch (ev.getAction()) {
case MotionEvent.ACTION_OUTSIDE:
hideMenu();
- break;
- case MotionEvent.ACTION_DOWN:
- mDownPosition.set(ev.getX(), ev.getY());
- mDownDelta.set(0f, 0f);
- break;
- case MotionEvent.ACTION_MOVE:
- mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
- if (mDownDelta.length() > mViewConfig.getScaledTouchSlop()
- && mMenuState != MENU_STATE_NONE) {
- // Restore the input consumer and let that drive the movement of this menu
- notifyRegisterInputConsumer();
- cancelDelayedFinish();
- }
- break;
+ return true;
}
return super.dispatchTouchEvent(ev);
}
@@ -384,7 +341,6 @@
if (allowMenuTimeout) {
repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
}
- notifyUnregisterInputConsumer();
}
}
@@ -509,11 +465,13 @@
actionView.setContentDescription(action.getContentDescription());
if (action.isEnabled()) {
actionView.setOnClickListener(v -> {
- try {
- action.getActionIntent().send();
- } catch (CanceledException e) {
- Log.w(TAG, "Failed to send action", e);
- }
+ mHandler.post(() -> {
+ try {
+ action.getActionIntent().send();
+ } catch (CanceledException e) {
+ Log.w(TAG, "Failed to send action", e);
+ }
+ });
});
}
actionView.setEnabled(action.isEnabled());
@@ -557,18 +515,6 @@
mBackgroundDrawable.setAlpha(alpha);
}
- private void notifyRegisterInputConsumer() {
- Message m = Message.obtain();
- m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER;
- sendMessage(m, "Could not notify controller to register input consumer");
- }
-
- private void notifyUnregisterInputConsumer() {
- Message m = Message.obtain();
- m.what = PipMenuActivityController.MESSAGE_UNREGISTER_INPUT_CONSUMER;
- sendMessage(m, "Could not notify controller to unregister input consumer");
- }
-
private void notifyMenuStateChange(int menuState) {
mMenuState = menuState;
Message m = Message.obtain();
@@ -586,11 +532,6 @@
}, false /* notifyMenuVisibility */, false /* isDismissing */);
}
- private void minimizePip() {
- sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
- "Could not notify controller to minimize PIP");
- }
-
private void dismissPip() {
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
@@ -600,12 +541,6 @@
}, false /* notifyMenuVisibility */, true /* isDismissing */);
}
- private void showPipMenu() {
- Message m = Message.obtain();
- m.what = PipMenuActivityController.MESSAGE_SHOW_MENU;
- sendMessage(m, "Could not notify controller to show PIP menu");
- }
-
private void showSettings() {
final Pair<ComponentName, Integer> topPipActivityInfo =
PipUtils.getTopPinnedActivity(this, ActivityManager.getService());
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index 46d53e4..57c04c3 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -37,6 +37,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+import android.view.MotionEvent;
import com.android.systemui.pip.phone.PipMediaController.ActionListener;
import com.android.systemui.shared.system.InputConsumerController;
@@ -156,14 +157,6 @@
mListeners.forEach(l -> l.onPipShowMenu());
break;
}
- case MESSAGE_REGISTER_INPUT_CONSUMER: {
- mInputConsumerController.registerInputConsumer();
- break;
- }
- case MESSAGE_UNREGISTER_INPUT_CONSUMER: {
- mInputConsumerController.unregisterInputConsumer();
- break;
- }
case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
mToActivityMessenger = msg.replyTo;
setStartActivityRequested(false);
@@ -212,15 +205,12 @@
}
public void onActivityPinned() {
- if (mMenuState == MENU_STATE_NONE) {
- // If the menu is not visible, then re-register the input consumer if it is not already
- // registered
- mInputConsumerController.registerInputConsumer();
- }
+ mInputConsumerController.registerInputConsumer();
}
public void onActivityUnpinned() {
hideMenu();
+ mInputConsumerController.unregisterInputConsumer();
setStartActivityRequested(false);
}
@@ -494,11 +484,7 @@
Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
+ " menuState=" + menuState + " resize=" + resize);
}
- if (menuState == MENU_STATE_NONE) {
- mInputConsumerController.registerInputConsumer();
- } else {
- mInputConsumerController.unregisterInputConsumer();
- }
+
if (menuState != mMenuState) {
mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize));
if (menuState == MENU_STATE_FULL) {
@@ -520,6 +506,22 @@
mStartActivityRequestedTime = requested ? SystemClock.uptimeMillis() : 0;
}
+ /**
+ * Handles touch event sent from pip input consumer.
+ */
+ void handleTouchEvent(MotionEvent ev) {
+ if (mToActivityMessenger != null) {
+ Message m = Message.obtain();
+ m.what = PipMenuActivity.MESSAGE_TOUCH_EVENT;
+ m.obj = ev;
+ try {
+ mToActivityMessenger.send(m);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not dispatch touch event", e);
+ }
+ }
+ }
+
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index b05058a..30cf412 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -363,6 +363,8 @@
// Update the touch state
mTouchState.onTouchEvent(ev);
+ boolean shouldDeliverToMenu = mMenuState != MENU_STATE_NONE;
+
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
mMotionHelper.synchronizePinnedStackBounds();
@@ -378,6 +380,8 @@
break;
}
}
+
+ shouldDeliverToMenu = !mTouchState.isDragging();
break;
}
case MotionEvent.ACTION_UP: {
@@ -394,6 +398,7 @@
// Fall through to clean up
}
case MotionEvent.ACTION_CANCEL: {
+ shouldDeliverToMenu = !mTouchState.startedDragging() && !mTouchState.isDragging();
mTouchState.reset();
break;
}
@@ -425,7 +430,20 @@
break;
}
}
- return mMenuState == MENU_STATE_NONE;
+
+ // Deliver the event to PipMenuActivity to handle button click if the menu has shown.
+ if (shouldDeliverToMenu) {
+ final MotionEvent cloneEvent = MotionEvent.obtain(ev);
+ // Send the cancel event and cancel menu timeout if it starts to drag.
+ if (mTouchState.startedDragging()) {
+ cloneEvent.setAction(MotionEvent.ACTION_CANCEL);
+ mMenuController.pokeMenu();
+ }
+
+ mMenuController.handleTouchEvent(cloneEvent);
+ }
+
+ return true;
}
/**
@@ -741,11 +759,11 @@
mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* updateListener */,
null /* animatorListener */);
setMinimizedStateInternal(false);
+ } else if (mTouchState.isDoubleTap()) {
+ // Expand to fullscreen if this is a double tap
+ mMotionHelper.expandPip();
} else if (mMenuState != MENU_STATE_FULL) {
- if (mTouchState.isDoubleTap()) {
- // Expand to fullscreen if this is a double tap
- mMotionHelper.expandPip();
- } else if (!mTouchState.isWaitingForDoubleTap()) {
+ if (!mTouchState.isWaitingForDoubleTap()) {
// User has stalled long enough for this not to be a drag or a double tap, just
// expand the menu
mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
@@ -756,9 +774,6 @@
// next tap
mTouchState.scheduleDoubleTapTimeoutCallback();
}
- } else {
- mMenuController.hideMenu();
- mMotionHelper.expandPip();
}
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 69efbc8..e3f65ef 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -106,6 +106,7 @@
mIsDoubleTap = !mPreviouslyDragging &&
(mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT;
mIsWaitingForDoubleTap = false;
+ mIsDragging = false;
mLastDownTouchTime = mDownTouchTime;
if (mDoubleTapTimeoutCallback != null) {
mHandler.removeCallbacks(mDoubleTapTimeoutCallback);