Merge "TV Pip fixes after menu migration to Window"

This commit is contained in:
Sergey Nikolaienkov
2020-12-09 08:28:44 +00:00
committed by Android (Google) Code Review
4 changed files with 97 additions and 248 deletions

View File

@@ -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,33 +519,8 @@ 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)";
return stateToName(mState);
}
private static String stateToName(int state) {

View File

@@ -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()");
void setOnBackPressListener(OnBackPressListener onBackPressListener) {
mOnBackPressListener = onBackPressListener;
}
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);
@Override
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);
}
hideMenu();
}
@Override
public void onPipEntered(String packageName) {
if (DEBUG) Log.d(TAG, "onPipEntered(), packageName=" + packageName);
}
@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();
}
}

View File

@@ -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();
void dismiss() {
mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP);
mNotified = false;
mPackageName = null;
}
@Override
public void onShowPipMenu() {
// no-op.
}
@Override
public void onMoveToFullscreen() {
dismissPipNotification();
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;

View File

@@ -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 {
}
}
@Override
public void attach(SurfaceControl leash) {
if (mMenuView == null) {
mMenuView = new PipMenuView(mContext, mPipController);
mSystemWindows.addView(mMenuView,
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
0, SHELL_ROOT_LAYER_PIP);
mLeash = leash;
void hideMenu() {
if (DEBUG) Log.d(TAG, "hideMenu()");
if (isMenuVisible()) {
mMenuView.hideMenu();
mPipController.resizePinnedStack(PipController.STATE_PIP);
}
}
@Override
public void attach(SurfaceControl leash) {
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);
}
private void detachPipMenuView() {
if (DEBUG) Log.d(TAG, "detachPipMenuView()");
if (mMenuView == null) {
return;
}
mSystemWindows.removeView(mMenuView);
mMenuView = null;
mLeash = null;
}
@Override
public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
mMenuView.setAppActions(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