Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c75ffe8c authored by Winson Chung's avatar Winson Chung
Browse files

Fixing some interaction issues with the PIP menu.



- Due to jank when finishing the PIP menu activity, we handle the
  menu visibility explicitly and keep the menu activity around
  once triggered (until PIP ends), instead of finishing and
  recreating it each time it is invoked.  This also gives us the
  flexibility to control how the animation looks of both the menu
  and individual actions.
- Allow dragging the PIP while the menu activity is showing and
  taking input
- Tapping outside of the PIP now hides the menu

Test: Enable the tap-to-interact in the SysUI tuner and drag while
      the menu is showing.

Change-Id: Iac74710100d793e6825b00c7c0d71b85fb420fa0
Signed-off-by: default avatarWinson Chung <winsonc@google.com>
parent dc143e01
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -427,6 +427,7 @@
            android:supportsPictureInPicture="true"
            android:stateNotNeeded="true"
            android:taskAffinity=""
            android:launchMode="singleTop"
            androidprv:alwaysFocusable="true" />

        <!-- platform logo easter egg activity -->
+3 −3
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@
    android:id="@+id/menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#33000000">
    android:background="#66000000">

    <LinearLayout
        android:id="@+id/actions"
@@ -45,7 +45,7 @@
            android:text="@string/pip_phone_dismiss"
            android:background="?android:selectableItemBackground" />
        <TextView
            android:id="@+id/minimize"
            android:id="@+id/expand"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
@@ -53,7 +53,7 @@
            android:textSize="12sp"
            android:textColor="#ffffffff"
            android:fontFamily="sans-serif"
            android:text="@string/pip_phone_minimize"
            android:text="@string/pip_phone_expand"
            android:background="?android:selectableItemBackground" />
    </LinearLayout>
</FrameLayout>
+1 −1
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@drawable/forced_resizable_background</item>
        <item name="android:windowBackground">@null</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:statusBarColor">@color/transparent</item>
        <item name="android:windowAnimationStyle">@style/Animation.PipPhoneOverlayControl</item>
+143 −28
Original line number Diff line number Diff line
@@ -16,13 +16,16 @@

package com.android.systemui.pip.phone;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.PointF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -30,11 +33,15 @@ import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
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.widget.ImageView;
import android.widget.TextView;

import com.android.systemui.Interpolators;
import com.android.systemui.R;

import java.util.ArrayList;
@@ -47,28 +54,40 @@ public class PipMenuActivity extends Activity {

    private static final String TAG = "PipMenuActivity";

    public static final int MESSAGE_FINISH_SELF = 1;
    public static final int MESSAGE_UPDATE_ACTIONS = 2;
    public static final int MESSAGE_SHOW_MENU = 1;
    public static final int MESSAGE_HIDE_MENU = 2;
    public static final int MESSAGE_UPDATE_ACTIONS = 3;

    private static final long INITIAL_DISMISS_DELAY = 2000;
    private static final long POST_INTERACTION_DISMISS_DELAY = 1500;
    private static final long MENU_FADE_DURATION = 125;

    private List<RemoteAction> mActions = new ArrayList<>();
    private boolean mMenuVisible;
    private final List<RemoteAction> mActions = new ArrayList<>();
    private View mMenuContainer;
    private View mDismissButton;
    private View mMinimizeButton;
    private View mExpandButton;

    private ObjectAnimator mMenuContainerAnimator;

    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() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_SHOW_MENU:
                    showMenu();
                    break;
                case MESSAGE_HIDE_MENU:
                    hideMenu();
                    break;
                case MESSAGE_UPDATE_ACTIONS:
                    setActions(((ParceledListSlice) msg.obj).getList());
                    break;
                case MESSAGE_FINISH_SELF:
                    finish();
                    break;
            }
        }
    });
@@ -76,12 +95,17 @@ public class PipMenuActivity extends Activity {
    private final Runnable mFinishRunnable = new Runnable() {
        @Override
        public void run() {
            finish();
            hideMenu();
        }
    };

    @Override
    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);
        getWindow().addFlags(LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | LayoutParams.FLAG_SLIPPERY);

        super.onCreate(savedInstanceState);
        setContentView(R.layout.pip_menu_activity);

@@ -94,44 +118,74 @@ public class PipMenuActivity extends Activity {
            setActions(actions.getList());
        }

        findViewById(R.id.menu).setOnClickListener((v) -> {
        mMenuContainer = findViewById(R.id.menu);
        mMenuContainer.setOnClickListener((v) -> {
            expandPip();
        });
        mDismissButton = findViewById(R.id.dismiss);
        mDismissButton.setOnClickListener((v) -> {
            dismissPip();
        });
        mMinimizeButton = findViewById(R.id.minimize);
        mMinimizeButton.setOnClickListener((v) -> {
            minimizePip();
        mExpandButton = findViewById(R.id.expand);
        mExpandButton.setOnClickListener((v) -> {
            expandPip();
        });

        notifyActivityCallback(mMessenger);
        showMenu();
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        showMenu();
    }

    @Override
    protected void onStart() {
        super.onStart();
        notifyActivityVisibility(true);
        notifyMenuVisibility(true);
        repostDelayedFinish(INITIAL_DISMISS_DELAY);
    }

    @Override
    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        if (!isInPictureInPictureMode) {
            finish();
        }
    }

    @Override
    public void onUserInteraction() {
        repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
    }

    @Override
    protected void onStop() {
        super.onStop();
        finish();
    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 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());
                break;
            case MotionEvent.ACTION_MOVE:
                mDownDelta.set(ev.getX() - mDownPosition.x, ev.getY() - mDownPosition.y);
                if (mDownDelta.length() > mViewConfig.getScaledTouchSlop() && mMenuVisible) {
                    hideMenu();
                    mMenuVisible = false;
                }
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public void finish() {
        View v = getWindow().getDecorView();
        v.removeCallbacks(mFinishRunnable);
        notifyActivityVisibility(false);
        notifyActivityCallback(null);
        super.finish();
        overridePendingTransition(0, R.anim.forced_resizable_exit);
        // Hide without an animation (the menu should already be invisible at this point)
        overridePendingTransition(0, 0);
    }

    @Override
@@ -139,6 +193,51 @@ public class PipMenuActivity extends Activity {
        // Do nothing
    }

    private void showMenu() {
        if (!mMenuVisible) {
            if (mMenuContainerAnimator != null) {
                mMenuContainerAnimator.cancel();
            }

            notifyMenuVisibility(true);
            mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                    mMenuContainer.getAlpha(), 1f);
            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    repostDelayedFinish(INITIAL_DISMISS_DELAY);
                }
            });
            mMenuContainerAnimator.start();
        }
    }

    private void hideMenu() {
        hideMenu(null /* animationFinishedRunnable */);
    }

    private void hideMenu(final Runnable animationFinishedRunnable) {
        if (mMenuVisible) {
            cancelDelayedFinish();
            notifyMenuVisibility(false);
            mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
                    mMenuContainer.getAlpha(), 0f);
            mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
            mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
            mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (animationFinishedRunnable != null) {
                        animationFinishedRunnable.run();
                    }
                }
            });
            mMenuContainerAnimator.start();
        }
    }

    private void setActions(List<RemoteAction> actions) {
        mActions.clear();
        mActions.addAll(actions);
@@ -173,17 +272,19 @@ public class PipMenuActivity extends Activity {
        }
    }

    private void notifyActivityVisibility(boolean visible) {
    private void notifyMenuVisibility(boolean visible) {
        mMenuVisible = visible;
        Message m = Message.obtain();
        m.what = PipMenuActivityController.MESSAGE_ACTIVITY_VISIBILITY_CHANGED;
        m.what = PipMenuActivityController.MESSAGE_MENU_VISIBILITY_CHANGED;
        m.arg1 = visible ? 1 : 0;
        m.replyTo = visible ? mMessenger : null;
        sendMessage(m, "Could not notify controller of PIP menu visibility");
    }

    private void expandPip() {
        hideMenu(() -> {
            sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
                    "Could not notify controller to expand PIP");
        });
    }

    private void minimizePip() {
@@ -192,8 +293,17 @@ public class PipMenuActivity extends Activity {
    }

    private void dismissPip() {
        hideMenu(() -> {
            sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
                    "Could not notify controller to dismiss PIP");
        });
    }

    private void notifyActivityCallback(Messenger callback) {
        Message m = Message.obtain();
        m.what = PipMenuActivityController.MESSAGE_UPDATE_ACTIVITY_CALLBACK;
        m.replyTo = callback;
        sendMessage(m, "Could not notify controller of activity finished");
    }

    private void sendEmptyMessage(int what, String errorMsg) {
@@ -210,6 +320,11 @@ public class PipMenuActivity extends Activity {
        }
    }

    private void cancelDelayedFinish() {
        View v = getWindow().getDecorView();
        v.removeCallbacks(mFinishRunnable);
    }

    private void repostDelayedFinish(long delay) {
        View v = getWindow().getDecorView();
        v.removeCallbacks(mFinishRunnable);
+40 −39
Original line number Diff line number Diff line
@@ -25,10 +25,11 @@ public class PipMenuActivityController {
    public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
    public static final String EXTRA_ACTIONS = "actions";

    public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 100;
    public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100;
    public static final int MESSAGE_EXPAND_PIP = 101;
    public static final int MESSAGE_MINIMIZE_PIP = 102;
    public static final int MESSAGE_DISMISS_PIP = 103;
    public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104;

    /**
     * A listener interface to receive notification on changes in PIP.
@@ -67,34 +68,25 @@ public class PipMenuActivityController {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_ACTIVITY_VISIBILITY_CHANGED: {
                case MESSAGE_MENU_VISIBILITY_CHANGED: {
                    boolean visible = msg.arg1 > 0;
                    int listenerCount = mListeners.size();
                    for (int i = 0; i < listenerCount; i++) {
                        mListeners.get(i).onPipMenuVisibilityChanged(visible);
                    }
                    mToActivityMessenger = msg.replyTo;
                    mListeners.forEach(l -> l.onPipMenuVisibilityChanged(visible));
                    break;
                }
                case MESSAGE_EXPAND_PIP: {
                    int listenerCount = mListeners.size();
                    for (int i = 0; i < listenerCount; i++) {
                        mListeners.get(i).onPipExpand();
                    }
                    mListeners.forEach(l -> l.onPipExpand());
                    break;
                }
                case MESSAGE_MINIMIZE_PIP: {
                    int listenerCount = mListeners.size();
                    for (int i = 0; i < listenerCount; i++) {
                        mListeners.get(i).onPipMinimize();
                    }
                    mListeners.forEach(l -> l.onPipMinimize());
                    break;
                }
                case MESSAGE_DISMISS_PIP: {
                    int listenerCount = mListeners.size();
                    for (int i = 0; i < listenerCount; i++) {
                        mListeners.get(i).onPipDismiss();
                    mListeners.forEach(l -> l.onPipDismiss());
                    break;
                }
                case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
                    mToActivityMessenger = msg.replyTo;
                    break;
                }
            }
@@ -121,6 +113,15 @@ public class PipMenuActivityController {
     * Shows the menu activity.
     */
    public void showMenu() {
        if (mToActivityMessenger != null) {
            Message m = Message.obtain();
            m.what = PipMenuActivity.MESSAGE_SHOW_MENU;
            try {
                mToActivityMessenger.send(m);
            } catch (RemoteException e) {
                Log.e(TAG, "Could not notify menu to show", e);
            }
        } else {
            // Start the menu activity on the top task of the pinned stack
            try {
                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
@@ -129,7 +130,7 @@ public class PipMenuActivityController {
                    Intent intent = new Intent(mContext, PipMenuActivity.class);
                    intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
                    intent.putExtra(EXTRA_ACTIONS, mActions);
                ActivityOptions options = ActivityOptions.makeBasic();
                    ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
                    options.setLaunchTaskId(
                            pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
                    options.setTaskOverlay(true);
@@ -141,6 +142,7 @@ public class PipMenuActivityController {
                Log.e(TAG, "Error showing PIP menu activity", e);
            }
        }
    }

    /**
     * Hides the menu activity.
@@ -148,13 +150,12 @@ public class PipMenuActivityController {
    public void hideMenu() {
        if (mToActivityMessenger != null) {
            Message m = Message.obtain();
            m.what = PipMenuActivity.MESSAGE_FINISH_SELF;
            m.what = PipMenuActivity.MESSAGE_HIDE_MENU;
            try {
                mToActivityMessenger.send(m);
            } catch (RemoteException e) {
                Log.e(TAG, "Could not notify menu activity to finish", e);
                Log.e(TAG, "Could not notify menu to hide", e);
            }
            mToActivityMessenger = null;
        }
    }

Loading