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

Commit 7bd311cf authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov Committed by Android (Google) Code Review
Browse files

Merge "TV Pip fixes after menu migration to Window"

parents 0f3b7c08 919cce15
Loading
Loading
Loading
Loading
+12 −137
Original line number Diff line number Diff line
@@ -37,7 +37,6 @@ import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Debug;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -56,8 +55,6 @@ import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
@@ -87,42 +84,21 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
    private static final int TASK_ID_NO_PIP = -1;
    private static final int INVALID_RESOURCE_TYPE = -1;

    public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;

    /**
     * PIPed activity is playing a media and it can be paused.
     */
    static final int PLAYBACK_STATE_PLAYING = 0;
    /**
     * PIPed activity has a paused media and it can be played.
     */
    static final int PLAYBACK_STATE_PAUSED = 1;
    /**
     * Users are unable to control PIPed activity's media playback.
     */
    static final int PLAYBACK_STATE_UNAVAILABLE = 2;

    private static final int CLOSE_PIP_WHEN_MEDIA_SESSION_GONE_TIMEOUT_MS = 3000;

    private int mSuspendPipResizingReason;

    private final Context mContext;
    private final PipBoundsState mPipBoundsState;
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    private final PipTaskOrganizer mPipTaskOrganizer;
    private final PipMediaController mPipMediaController;
    private final TvPipMenuController mTvPipMenuController;
    private final PipNotification mPipNotification;

    private IActivityTaskManager mActivityTaskManager;
    private int mState = STATE_NO_PIP;
    private int mResumeResizePinnedStackRunnableState = STATE_NO_PIP;
    private final Handler mHandler = new Handler();
    private List<Listener> mListeners = new ArrayList<>();
    private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
    private int mPipTaskId = TASK_ID_NO_PIP;
    private int mPinnedStackId = INVALID_STACK_ID;
    private String[] mLastPackagesResourceGranted;
    private PipNotification mPipNotification;
    private ParceledListSlice<RemoteAction> mCustomActions;
    private WindowManagerShellWrapper mWindowManagerShellWrapper;
    private int mResizeAnimationDuration;
@@ -135,9 +111,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
    private boolean mImeVisible;
    private int mImeHeightAdjustment;

    private final Runnable mResizePinnedStackRunnable =
            () -> resizePinnedStack(mResumeResizePinnedStackRunnableState);
    private final Runnable mClosePipRunnable = () -> closePip();
    private final Runnable mClosePipRunnable = this::closePip;
    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
@@ -237,8 +211,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        mPipTaskOrganizer.registerPipTransitionCallback(this);
        mActivityTaskManager = ActivityTaskManager.getService();

        addListener(mPipNotification);

        final IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION_CLOSE);
        intentFilter.addAction(ACTION_MENU);
@@ -340,9 +312,8 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
                mPinnedStackId = INVALID_STACK_ID;
            }
        }
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onPipActivityClosed();
        }
        mPipNotification.dismiss();
        mTvPipMenuController.hideMenu();
        mHandler.removeCallbacks(mClosePipRunnable);
    }

@@ -353,9 +324,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());

        mPipTaskId = TASK_ID_NO_PIP;
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onMoveToFullscreen();
        }
        mTvPipMenuController.hideMenu();
        mPipNotification.dismiss();

        resizePinnedStack(STATE_NO_PIP);
    }

@@ -379,9 +350,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        // Set state to STATE_PIP so we show it when the pinned stack animation ends.
        mState = STATE_PIP;
        mPipMediaController.onActivityPinned();
        for (int i = mListeners.size() - 1; i >= 0; i--) {
            mListeners.get(i).onPipEntered(packageName);
        }
        mPipNotification.show(packageName);
    }

    private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
@@ -427,62 +396,18 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        }
    }

    /**
     * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
     *
     * @param reason The reason for suspending resizing operations on the Pip.
     */
    public void suspendPipResizing(int reason) {
        if (DEBUG) {
            Log.d(TAG,
                    "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
        }
        mSuspendPipResizingReason |= reason;
    }

    /**
     * Resumes resizing operation on the Pip that was previously suspended.
     *
     * @param reason The reason resizing operations on the Pip was suspended.
     */
    public void resumePipResizing(int reason) {
        if ((mSuspendPipResizingReason & reason) == 0) {
            return;
        }
        if (DEBUG) {
            Log.d(TAG,
                    "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
        }
        mSuspendPipResizingReason &= ~reason;
        mHandler.post(mResizePinnedStackRunnable);
    }

    /**
     * Resize the Pip to the appropriate size for the input state.
     *
     * @param state In Pip state also used to determine the new size for the Pip.
     */
    public void resizePinnedStack(int state) {

        if (DEBUG) {
            Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
                    + getStateDescription(), new Exception());
        }

        boolean wasStateNoPip = (mState == STATE_NO_PIP);
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onPipResizeAboutToStart();
        }
        if (mSuspendPipResizingReason != 0) {
            mResumeResizePinnedStackRunnableState = state;
            if (DEBUG) {
                Log.d(TAG, "resizePinnedStack() deferring"
                        + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
                        + " mResumeResizePinnedStackRunnableState="
                        + stateToName(mResumeResizePinnedStackRunnableState));
            }
            return;
        }
        final boolean wasStateNoPip = (mState == STATE_NO_PIP);
        mTvPipMenuController.hideMenu();
        mState = state;
        final Rect newBounds;
        switch (mState) {
@@ -510,44 +435,19 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
    }

    /**
     * @return the current state, or the pending state if the state change was previously suspended.
     * @return the current state.
     */
    private int getState() {
        if (mSuspendPipResizingReason != 0) {
            return mResumeResizePinnedStackRunnableState;
        }
        return mState;
    }

    /**
     * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
     * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
     */
    private void showPipMenu() {
        if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());

        mState = STATE_PIP_MENU;
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onShowPipMenu();
        }

        mTvPipMenuController.showMenu();
    }

    /**
     * Adds a {@link Listener} to PipController.
     */
    void addListener(Listener listener) {
        mListeners.add(listener);
    }

    /**
     * Removes a {@link Listener} from PipController.
     */
    void removeListener(Listener listener) {
        mListeners.remove(listener);
    }

    /**
     * Returns {@code true} if PIP is shown.
     */
@@ -619,34 +519,9 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
        }
    }

    /**
     * A listener interface to receive notification on changes in PIP.
     */
    public interface Listener {
        /**
         * Invoked when an activity is pinned and PIP manager is set corresponding information.
         * Classes must use this instead of {@link android.app.ITaskStackListener.onActivityPinned}
         * because there's no guarantee for the PIP manager be return relavent information
         * correctly. (e.g. {@link Pip.isPipShown}).
         */
        void onPipEntered(String packageName);
        /** Invoked when a PIPed activity is closed. */
        void onPipActivityClosed();
        /** Invoked when the PIP menu gets shown. */
        void onShowPipMenu();
        /** Invoked when the PIPed activity is about to return back to the fullscreen. */
        void onMoveToFullscreen();
        /** Invoked when we are above to start resizing the Pip. */
        void onPipResizeAboutToStart();
    }

    private String getStateDescription() {
        if (mSuspendPipResizingReason == 0) {
        return stateToName(mState);
    }
        return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState)
                + " is suspended)";
    }

    private static String stateToName(int state) {
        switch (state) {
+25 −69
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.wm.shell.pip.tv;

import static android.view.KeyEvent.ACTION_UP;
import static android.view.KeyEvent.KEYCODE_BACK;

import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.annotation.Nullable;
@@ -36,25 +39,22 @@ import java.util.Collections;
/**
 * The Menu View that shows controls of the PiP. Always fullscreen.
 */
public class PipMenuView extends FrameLayout implements PipController.Listener {
public class PipMenuView extends FrameLayout {
    private static final String TAG = "PipMenuView";
    private static final boolean DEBUG = PipController.DEBUG;

    private final PipController mPipController;
    private final Animator mFadeInAnimation;
    private final Animator mFadeOutAnimation;
    private final PipControlsViewController mPipControlsViewController;
    private boolean mRestorePipSizeWhenClose;
    @Nullable
    private OnBackPressListener mOnBackPressListener;

    public PipMenuView(Context context, PipController pipController) {
        super(context, null, 0);
        mPipController = pipController;

        inflate(context, R.layout.tv_pip_menu, this);

        mPipControlsViewController = new PipControlsViewController(
                findViewById(R.id.pip_controls), mPipController);
        mRestorePipSizeWhenClose = true;
                findViewById(R.id.pip_controls), pipController);
        mFadeInAnimation = AnimatorInflater.loadAnimator(
                mContext, R.anim.tv_pip_menu_fade_in_animation);
        mFadeInAnimation.setTarget(mPipControlsViewController.getView());
@@ -63,16 +63,6 @@ public class PipMenuView extends FrameLayout implements PipController.Listener {
        mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_UP) {
            restorePipAndFinish();
            return true;
        }
        return super.dispatchKeyEvent(event);
    }

    @Nullable
    SurfaceControl getWindowSurfaceControl() {
        final ViewRootImpl root = getViewRootImpl();
@@ -87,53 +77,39 @@ public class PipMenuView extends FrameLayout implements PipController.Listener {
    }

    void showMenu() {
        mPipController.addListener(this);
        mFadeInAnimation.start();
        setAlpha(1.0f);
        try {
            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
                    getViewRootImpl().getInputToken(), true /* grantFocus */);
        } catch (Exception e) {
            Log.e(TAG, "Unable to update focus as menu appears", e);
        }
        grantWindowFocus(true);
    }

    void hideMenu() {
        mPipController.removeListener(this);
        mPipController.resumePipResizing(
                PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
        mFadeOutAnimation.start();
        setAlpha(0.0f);
        grantWindowFocus(false);
    }

    private void grantWindowFocus(boolean grantFocus) {
        try {
            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
                    getViewRootImpl().getInputToken(), false /* grantFocus */);
                    getViewRootImpl().getInputToken(), grantFocus);
        } catch (Exception e) {
            Log.e(TAG, "Unable to update focus as menu disappears", e);
        }
    }

    private void restorePipAndFinish() {
        if (DEBUG) Log.d(TAG, "restorePipAndFinish()");

        if (mRestorePipSizeWhenClose) {
            if (DEBUG) Log.d(TAG, "   > restoring to the default position");

            // When PIP menu activity is closed, restore to the default position.
            mPipController.resizePinnedStack(PipController.STATE_PIP);
        }
        hideMenu();
    void setOnBackPressListener(OnBackPressListener onBackPressListener) {
        mOnBackPressListener = onBackPressListener;
    }

    @Override
    public void onPipEntered(String packageName) {
        if (DEBUG) Log.d(TAG, "onPipEntered(), packageName=" + packageName);
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KEYCODE_BACK && event.getAction() == ACTION_UP
                && mOnBackPressListener != null) {
            mOnBackPressListener.onBackPress();
            return true;
        } else {
            return super.dispatchKeyEvent(event);
        }

    @Override
    public void onPipActivityClosed() {
        if (DEBUG) Log.d(TAG, "onPipActivityClosed()");

        hideMenu();
    }

    void setAppActions(ParceledListSlice<RemoteAction> actions) {
@@ -144,27 +120,7 @@ public class PipMenuView extends FrameLayout implements PipController.Listener {
                hasCustomActions ? actions.getList() : Collections.emptyList());
    }

    @Override
    public void onShowPipMenu() {
        if (DEBUG) Log.d(TAG, "onShowPipMenu()");
    }

    @Override
    public void onMoveToFullscreen() {
        if (DEBUG) Log.d(TAG, "onMoveToFullscreen()");

        // Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
        // This conflicts with restoring PIP position, so disable it.
        mRestorePipSizeWhenClose = false;
        hideMenu();
    }

    @Override
    public void onPipResizeAboutToStart() {
        if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");

        hideMenu();
        mPipController.suspendPipResizing(
                PipController.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
    interface OnBackPressListener {
        void onBackPress();
    }
}
+9 −31
Original line number Diff line number Diff line
@@ -39,7 +39,7 @@ import java.util.Objects;
 * <p>Once it's created, it will manage the PIP notification UI by itself except for handling
 * configuration changes.
 */
public class PipNotification implements PipController.Listener {
public class PipNotification {
    private static final boolean DEBUG = PipController.DEBUG;
    private static final String TAG = "PipNotification";

@@ -79,38 +79,21 @@ public class PipNotification implements PipController.Listener {
        onConfigurationChanged(context);
    }

    @Override
    public void onPipEntered(String packageName) {
    void show(String packageName) {
        mPackageName = packageName;
        notifyPipNotification();
        update();
    }

    @Override
    public void onPipActivityClosed() {
        dismissPipNotification();
        mPackageName = null;
    }

    @Override
    public void onShowPipMenu() {
        // no-op.
    }

    @Override
    public void onMoveToFullscreen() {
        dismissPipNotification();
    void dismiss() {
        mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
        mNotified = false;
        mPackageName = null;
    }

    @Override
    public void onPipResizeAboutToStart() {
        // no-op.
    }

    private void onMediaMetadataChanged(MediaMetadata metadata) {
        if (updateMediaControllerMetadata(metadata) && mNotified) {
            // update notification
            notifyPipNotification();
            update();
        }
    }

@@ -123,11 +106,11 @@ public class PipNotification implements PipController.Listener {
        mDefaultIconResId = R.drawable.pip_icon;
        if (mNotified) {
            // update notification
            notifyPipNotification();
            update();
        }
    }

    private void notifyPipNotification() {
    private void update() {
        mNotified = true;
        mNotificationBuilder
                .setShowWhen(true)
@@ -144,11 +127,6 @@ public class PipNotification implements PipController.Listener {
                mNotificationBuilder.build());
    }

    private void dismissPipNotification() {
        mNotified = false;
        mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
    }

    private boolean updateMediaControllerMetadata(MediaMetadata metadata) {
        String title = null;
        Bitmap art = null;
+49 −9
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import android.app.RemoteAction;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.util.Log;
import android.view.SurfaceControl;

import com.android.wm.shell.common.SystemWindows;
@@ -31,6 +32,8 @@ import com.android.wm.shell.pip.PipMenuController;
 * Manages the visibility of the PiP Menu as user interacts with PiP.
 */
public class TvPipMenuController implements PipMenuController {
    private static final String TAG = "TvPipMenuController";
    private static final boolean DEBUG = PipController.DEBUG;

    private final Context mContext;
    private final SystemWindows mSystemWindows;
@@ -52,6 +55,8 @@ public class TvPipMenuController implements PipMenuController {

    @Override
    public void showMenu() {
        if (DEBUG) Log.d(TAG, "showMenu()");

        if (mMenuView != null) {
            mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
                    mPipBoundsState.getDisplayBounds().width(),
@@ -68,27 +73,62 @@ public class TvPipMenuController implements PipMenuController {
        }
    }

    void hideMenu() {
        if (DEBUG) Log.d(TAG, "hideMenu()");

        if (isMenuVisible()) {
            mMenuView.hideMenu();
            mPipController.resizePinnedStack(PipController.STATE_PIP);
        }
    }

    @Override
    public void attach(SurfaceControl leash) {
        if (mMenuView == null) {
        mLeash = leash;
        attachPipMenuView();
    }

    @Override
    public void detach() {
        hideMenu();
        detachPipMenuView();
        mLeash = null;
    }

    private void attachPipMenuView() {
        if (DEBUG) Log.d(TAG, "attachPipMenuView()");

        if (mMenuView != null) {
            detachPipMenuView();
        }

        mMenuView = new PipMenuView(mContext, mPipController);
        mMenuView.setOnBackPressListener(this::hideMenu);
        mSystemWindows.addView(mMenuView,
                getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                0, SHELL_ROOT_LAYER_PIP);
            mLeash = leash;
    }

    private void detachPipMenuView() {
        if (DEBUG) Log.d(TAG, "detachPipMenuView()");

        if (mMenuView == null) {
            return;
        }

    @Override
    public void detach() {
        mSystemWindows.removeView(mMenuView);
        mMenuView = null;
        mLeash = null;
    }

    @Override
    public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
        if (DEBUG) Log.d(TAG, "setAppActions(), actions=" + appActions);

        if (mMenuView != null) {
            mMenuView.setAppActions(appActions);
        } else {
            Log.w(TAG, "Cannot set remote actions, there is no View");
        }
    }

    @Override