Re-implement TvPipController

Re-working TvPipController (formerly tv.PipController) taking into
account recent migration to WmShell.

Bug: 153784643
Test: atest WMShellFlickerTests
Change-Id: I3657efccece24e12783b582db26ebe93468c6fa5
This commit is contained in:
Sergey Nikolaienkov
2020-12-23 09:46:05 +00:00
parent 068369c26a
commit dd86cfbd57
6 changed files with 437 additions and 548 deletions

View File

@@ -1,540 +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.tv;
import static android.app.ActivityTaskManager.INVALID_STACK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.Intent.ACTION_MEDIA_RESOURCE_GRANTED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.IActivityTaskManager;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.DisplayInfo;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.util.Objects;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
/**
* Unknown or invalid state
*/
public static final int STATE_UNKNOWN = -1;
/**
* State when there's no PIP.
*/
public static final int STATE_NO_PIP = 0;
/**
* State when PIP is shown. This is used as default PIP state.
*/
public static final int STATE_PIP = 1;
/**
* State when PIP menu dialog is shown.
*/
public static final int STATE_PIP_MENU = 2;
private static final int TASK_ID_NO_PIP = -1;
private static final int INVALID_RESOURCE_TYPE = -1;
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 TvPipNotificationController mPipNotificationController;
private IActivityTaskManager mActivityTaskManager;
private int mState = STATE_NO_PIP;
private final Handler mHandler = new Handler();
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
private int mPipTaskId = TASK_ID_NO_PIP;
private int mPinnedStackId = INVALID_STACK_ID;
private String[] mLastPackagesResourceGranted;
private ParceledListSlice<RemoteAction> mCustomActions;
private WindowManagerShellWrapper mWindowManagerShellWrapper;
private int mResizeAnimationDuration;
// Used to calculate the movement bounds
private final DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private final Rect mTmpInsetBounds = new Rect();
// Keeps track of the IME visibility to adjust the PiP when the IME is visible
private boolean mImeVisible;
private int mImeHeightAdjustment;
private final Runnable mClosePipRunnable = this::closePip;
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) {
Log.d(TAG, "mBroadcastReceiver, action: " + intent.getAction());
}
switch (intent.getAction()) {
case ACTION_MEDIA_RESOURCE_GRANTED:
String[] packageNames = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
int resourceType = intent.getIntExtra(Intent.EXTRA_MEDIA_RESOURCE_TYPE,
INVALID_RESOURCE_TYPE);
if (packageNames != null && packageNames.length > 0
&& resourceType == Intent.EXTRA_MEDIA_RESOURCE_TYPE_VIDEO_CODEC) {
handleMediaResourceGranted(packageNames);
}
break;
}
}
};
private final PinnedStackListenerForwarder.PinnedStackListener mPinnedStackListener =
new PipControllerPinnedStackListener();
@Override
public void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
/**
* Handler for messages from the PIP controller.
*/
private class PipControllerPinnedStackListener extends
PinnedStackListenerForwarder.PinnedStackListener {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
if (mState == STATE_PIP) {
if (mImeVisible != imeVisible) {
if (imeVisible) {
// Save the IME height adjustment, and offset to not occlude the IME
mPipBoundsState.getNormalBounds().offset(0, -imeHeight);
mImeHeightAdjustment = imeHeight;
} else {
// Apply the inverse adjustment when the IME is hidden
mPipBoundsState.getNormalBounds().offset(0, mImeHeightAdjustment);
}
mImeVisible = imeVisible;
resizePinnedStack(STATE_PIP);
}
}
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
mTmpDisplayInfo.copyFrom(mPipBoundsState.getDisplayInfo());
mPipBoundsAlgorithm.getInsetBounds(mTmpInsetBounds);
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
mCustomActions = actions;
mTvPipMenuController.setAppActions(mCustomActions);
}
}
public PipController(Context context,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController tvPipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper windowManagerShellWrapper) {
mContext = context;
mPipBoundsState = pipBoundsState;
mPipNotificationController = tvPipNotificationController;
mPipNotificationController.setDelegate(this);
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipMediaController = pipMediaController;
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
// Ensure that we have the display info in case we get calls to update the bounds
// before the listener calls back
final DisplayInfo displayInfo = new DisplayInfo();
context.getDisplay().getDisplayInfo(displayInfo);
mPipBoundsState.setDisplayInfo(displayInfo);
mResizeAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mPipTaskOrganizer = pipTaskOrganizer;
mPipTaskOrganizer.registerPipTransitionCallback(this);
mActivityTaskManager = ActivityTaskManager.getService();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_MEDIA_RESOURCE_GRANTED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter, UserHandle.USER_ALL);
// Initialize the last orientation and apply the current configuration
Configuration initialConfig = mContext.getResources().getConfiguration();
mLastOrientation = initialConfig.orientation;
loadConfigurationsAndApply(initialConfig);
mWindowManagerShellWrapper = windowManagerShellWrapper;
try {
mWindowManagerShellWrapper.addPinnedStackListener(mPinnedStackListener);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
// Handle for system task stack changes.
taskStackListener.addListener(
new TaskStackListenerCallback() {
@Override
public void onTaskStackChanged() {
PipController.this.onTaskStackChanged();
}
@Override
public void onActivityPinned(String packageName, int userId, int taskId,
int stackId) {
PipController.this.onActivityPinned(packageName);
}
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
PipController.this.onActivityRestartAttempt(task, clearedTask);
}
});
}
private void loadConfigurationsAndApply(Configuration newConfig) {
if (mLastOrientation != newConfig.orientation) {
// Don't resize the pinned stack on orientation change. TV does not care about this case
// and this could clobber the existing animation to the new bounds calculated by WM.
mLastOrientation = newConfig.orientation;
return;
}
final Rect menuBounds = Rect.unflattenFromString(
mContext.getResources().getString(R.string.pip_menu_bounds));
mPipBoundsState.setExpandedBounds(menuBounds);
resizePinnedStack(getPinnedTaskInfo() == null ? STATE_NO_PIP : STATE_PIP);
}
/**
* Updates the PIP per configuration changed.
*/
@Override
public void onConfigurationChanged(Configuration newConfig) {
loadConfigurationsAndApply(newConfig);
mPipNotificationController.onConfigurationChanged(mContext);
}
/**
* Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
*/
public void showPictureInPictureMenu() {
if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription());
if (getState() == STATE_PIP) {
resizePinnedStack(STATE_PIP_MENU);
}
}
/**
* Closes PIP (PIPed activity and PIP system UI).
*/
@Override
public void closePip() {
if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
closePipInternal(true);
}
private void closePipInternal(boolean removePipStack) {
if (DEBUG) {
Log.d(TAG,
"closePipInternal() removePipStack=" + removePipStack + ", current state="
+ getStateDescription());
}
mState = STATE_NO_PIP;
mPipTaskId = TASK_ID_NO_PIP;
if (removePipStack) {
try {
mActivityTaskManager.removeTask(mPinnedStackId);
} catch (RemoteException e) {
Log.e(TAG, "removeTask failed", e);
} finally {
mPinnedStackId = INVALID_STACK_ID;
}
}
mPipNotificationController.dismiss();
mTvPipMenuController.hideMenu();
mHandler.removeCallbacks(mClosePipRunnable);
}
@Override
public void movePipToNormalPosition() {
resizePinnedStack(PipController.STATE_PIP);
}
/**
* Moves the PIPed activity to the fullscreen and closes PIP system UI.
*/
@Override
public void movePipToFullscreen() {
if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
mPipTaskId = TASK_ID_NO_PIP;
mTvPipMenuController.hideMenu();
mPipNotificationController.dismiss();
resizePinnedStack(STATE_NO_PIP);
}
private void onActivityPinned(String packageName) {
final RootTaskInfo taskInfo = getPinnedTaskInfo();
if (DEBUG) Log.d(TAG, "onActivityPinned, task=" + taskInfo);
if (taskInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
return;
}
// At this point PipBoundsState knows the correct aspect ratio for this pinned task, so we
// use PipBoundsAlgorithm to calculate the normal bounds for the task (PipBoundsAlgorithm
// will query PipBoundsState for the aspect ratio) and pass the bounds over to the
// PipBoundsState.
mPipBoundsState.setNormalBounds(mPipBoundsAlgorithm.getNormalBounds());
mPinnedStackId = taskInfo.taskId;
mPipTaskId = taskInfo.childTaskIds[taskInfo.childTaskIds.length - 1];
// Set state to STATE_PIP so we show it when the pinned stack animation ends.
mState = STATE_PIP;
mPipMediaController.onActivityPinned();
mPipNotificationController.show(packageName);
}
private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean clearedTask) {
if (task.getWindowingMode() != WINDOWING_MODE_PINNED) {
return;
}
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
// If PIPed activity is launched again by Launcher or intent, make it fullscreen.
movePipToFullscreen();
}
private void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
if (getState() != STATE_NO_PIP) {
boolean hasPip = false;
RootTaskInfo taskInfo = getPinnedTaskInfo();
if (taskInfo == null || taskInfo.childTaskIds == null) {
Log.w(TAG, "There is nothing in pinned stack");
closePipInternal(false);
return;
}
for (int i = taskInfo.childTaskIds.length - 1; i >= 0; --i) {
if (taskInfo.childTaskIds[i] == mPipTaskId) {
// PIP task is still alive.
hasPip = true;
break;
}
}
if (!hasPip) {
// PIP task doesn't exist anymore in PINNED_STACK.
closePipInternal(true);
return;
}
}
if (getState() == STATE_PIP) {
if (!Objects.equals(mPipBoundsState.getBounds(), mPipBoundsState.getNormalBounds())) {
resizePinnedStack(STATE_PIP);
}
}
}
/**
* 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());
}
final boolean wasStateNoPip = (mState == STATE_NO_PIP);
mTvPipMenuController.hideMenu();
mState = state;
final Rect newBounds;
switch (mState) {
case STATE_NO_PIP:
newBounds = null;
// If the state was already STATE_NO_PIP, then do not resize the stack below as it
// will not exist
if (wasStateNoPip) {
return;
}
break;
case STATE_PIP_MENU:
newBounds = mPipBoundsState.getExpandedBounds();
break;
case STATE_PIP: // fallthrough
default:
newBounds = mPipBoundsState.getNormalBounds();
break;
}
if (newBounds != null) {
mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
} else {
mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
}
}
/**
* @return the current state.
*/
private int getState() {
return mState;
}
private void showPipMenu() {
if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
mState = STATE_PIP_MENU;
mTvPipMenuController.showMenu();
}
/**
* Returns {@code true} if PIP is shown.
*/
public boolean isPipShown() {
return mState != STATE_NO_PIP;
}
private RootTaskInfo getPinnedTaskInfo() {
RootTaskInfo taskInfo = null;
try {
taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
} catch (RemoteException e) {
Log.e(TAG, "getRootTaskInfo failed", e);
}
if (DEBUG) Log.d(TAG, "getPinnedTaskInfo(), taskInfo=" + taskInfo);
return taskInfo;
}
private void handleMediaResourceGranted(String[] packageNames) {
if (getState() == STATE_NO_PIP) {
mLastPackagesResourceGranted = packageNames;
} else {
boolean requestedFromLastPackages = false;
if (mLastPackagesResourceGranted != null) {
for (String packageName : mLastPackagesResourceGranted) {
for (String newPackageName : packageNames) {
if (TextUtils.equals(newPackageName, packageName)) {
requestedFromLastPackages = true;
break;
}
}
}
}
mLastPackagesResourceGranted = packageNames;
if (!requestedFromLastPackages) {
closePip();
}
}
}
@Override
public void hidePipMenu(Runnable onStartCallback, Runnable onEndCallback) {
}
PipMediaController getPipMediaController() {
return mPipMediaController;
}
@Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
}
@Override
public void onPipTransitionFinished(ComponentName activity, int direction) {
onPipTransitionFinishedOrCanceled();
}
@Override
public void onPipTransitionCanceled(ComponentName activity, int direction) {
onPipTransitionFinishedOrCanceled();
}
private void onPipTransitionFinishedOrCanceled() {
if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()");
if (getState() == STATE_PIP_MENU) {
showPipMenu();
}
}
private String getStateDescription() {
return stateToName(mState);
}
private static String stateToName(int state) {
switch (state) {
case STATE_NO_PIP:
return "NO_PIP";
case STATE_PIP:
return "PIP";
case STATE_PIP_MENU:
return "PIP_MENU";
default:
return "UNKNOWN(" + state + ")";
}
}
}

View File

@@ -0,0 +1,421 @@
/*
* 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.tv;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.RemoteAction;
import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
import android.view.DisplayInfo;
import com.android.wm.shell.R;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipTaskOrganizer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Manages the picture-in-picture (PIP) UI and states.
*/
public class TvPipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
TvPipMenuController.Delegate, TvPipNotificationController.Delegate {
private static final String TAG = "TvPipController";
static final boolean DEBUG = true;
private static final int NONEXISTENT_TASK_ID = -1;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "STATE_" }, value = {
STATE_NO_PIP,
STATE_PIP,
STATE_PIP_MENU
})
public @interface State {}
/**
* State when there is no applications in Pip.
*/
private static final int STATE_NO_PIP = 0;
/**
* State when there is an applications in Pip and the Pip window located at its "normal" place
* (usually the bottom right corner).
*/
private static final int STATE_PIP = 1;
/**
* State when there is an applications in Pip and the Pip menu is open. In this state Pip window
* is usually moved from its "normal" position on the screen to the "menu" position - which is
* often at the middle of the screen, and gets slightly scaled up.
*/
private static final int STATE_PIP_MENU = 2;
private final Context mContext;
private final PipBoundsState mPipBoundsState;
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PipMediaController mPipMediaController;
private final TvPipNotificationController mPipNotificationController;
private final TvPipMenuController mTvPipMenuController;
private @State int mState = STATE_NO_PIP;
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private int mResizeAnimationDuration;
public TvPipController(
Context context,
PipBoundsState pipBoundsState,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipTaskOrganizer pipTaskOrganizer,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper wmShell) {
mContext = context;
mPipBoundsState = pipBoundsState;
mPipBoundsState.setDisplayInfo(getDisplayInfo());
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipMediaController = pipMediaController;
mPipNotificationController = pipNotificationController;
mPipNotificationController.setDelegate(this);
mTvPipMenuController = tvPipMenuController;
mTvPipMenuController.setDelegate(this);
mPipTaskOrganizer = pipTaskOrganizer;
mPipTaskOrganizer.registerPipTransitionCallback(this);
loadConfigurations();
registerTaskStackListenerCallback(taskStackListener);
registerWmShellPinnedStackListener(wmShell);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG) Log.d(TAG, "onConfigurationChanged(), state=" + stateToName(mState));
if (isPipShown()) {
if (DEBUG) Log.d(TAG, " > closing Pip.");
closePip();
}
loadConfigurations();
mPipNotificationController.onConfigurationChanged(mContext);
}
/**
* Returns {@code true} if Pip is shown.
*/
@Override
public boolean isPipShown() {
return mState != STATE_NO_PIP;
}
/**
* Starts the process if bringing up the Pip menu if by issuing a command to move Pip
* task/window to the "Menu" position. We'll show the actual Menu UI (eg. actions) once the Pip
* task/window is properly positioned in {@link #onPipTransitionFinished(ComponentName, int)}.
*/
@Override
public void showPictureInPictureMenu() {
if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), state=" + stateToName(mState));
if (mState != STATE_PIP) {
if (DEBUG) Log.d(TAG, " > cannot open Menu from the current state.");
return;
}
setState(STATE_PIP_MENU);
resizePinnedStack(STATE_PIP_MENU);
}
/**
* Moves Pip window to its "normal" position.
*/
@Override
public void movePipToNormalPosition() {
if (DEBUG) Log.d(TAG, "movePipToNormalPosition(), state=" + stateToName(mState));
setState(STATE_PIP);
resizePinnedStack(STATE_PIP);
}
/**
* Opens the "Pip-ed" Activity fullscreen.
*/
@Override
public void movePipToFullscreen() {
if (DEBUG) Log.d(TAG, "movePipToFullscreen(), state=" + stateToName(mState));
mPipTaskOrganizer.exitPip(mResizeAnimationDuration);
onPipDisappeared();
}
/**
* Closes Pip window.
*/
@Override
public void closePip() {
if (DEBUG) Log.d(TAG, "closePip(), state=" + stateToName(mState));
removeTask(mPinnedTaskId);
onPipDisappeared();
}
/**
* Resizes the Pip task/window to the appropriate size for the given state.
* This is a legacy API. Now we expect that the state argument passed to it should always match
* the current state of the Controller. If it does not match an {@link IllegalArgumentException}
* will be thrown. However, if the passed state does match - we'll determine the right bounds
* to the state and will move Pip task/window there.
*
* @param state the to determine the Pip bounds. IMPORTANT: should always match the current
* state of the Controller.
*/
@Override
public void resizePinnedStack(@State int state) {
if (state != mState) {
throw new IllegalArgumentException("The passed state should match the current state!");
}
if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + stateToName(mState));
final Rect newBounds;
switch (mState) {
case STATE_PIP_MENU:
newBounds = mPipBoundsState.getExpandedBounds();
break;
case STATE_PIP:
// Let PipBoundsAlgorithm figure out what the correct bounds are at the moment.
// Internally, it will get the "default" bounds from PipBoundsState and adjust them
// as needed to account for things like IME state (will query PipBoundsState for
// this information as well, so it's important to keep PipBoundsState up to date).
newBounds = mPipBoundsAlgorithm.getNormalBounds();
break;
case STATE_NO_PIP:
default:
return;
}
mPipTaskOrganizer.scheduleAnimateResizePip(newBounds, mResizeAnimationDuration, null);
}
@Override
public void registerSessionListenerForCurrentUser() {
mPipMediaController.registerSessionListenerForCurrentUser();
}
private void checkIfPinnedTaskAppeared() {
final TaskInfo pinnedTask = getPinnedTaskInfo();
if (DEBUG) Log.d(TAG, "checkIfPinnedTaskAppeared(), task=" + pinnedTask);
if (pinnedTask == null) return;
mPinnedTaskId = pinnedTask.taskId;
setState(STATE_PIP);
mPipMediaController.onActivityPinned();
mPipNotificationController.show(pinnedTask.topActivity.getPackageName());
}
private void checkIfPinnedTaskIsGone() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
if (isPipShown() && getPinnedTaskInfo() == null) {
Log.w(TAG, "Pinned task is gone.");
onPipDisappeared();
}
}
private void onPipDisappeared() {
if (DEBUG) Log.d(TAG, "onPipDisappeared() state=" + stateToName(mState));
mPipNotificationController.dismiss();
mTvPipMenuController.hideMenu();
setState(STATE_NO_PIP);
mPinnedTaskId = NONEXISTENT_TASK_ID;
}
@Override
public void onPipTransitionStarted(ComponentName activity, int direction, Rect pipBounds) {
if (DEBUG) Log.d(TAG, "onPipTransition_Started(), state=" + stateToName(mState));
}
@Override
public void onPipTransitionCanceled(ComponentName activity, int direction) {
if (DEBUG) Log.d(TAG, "onPipTransition_Canceled(), state=" + stateToName(mState));
}
@Override
public void onPipTransitionFinished(ComponentName activity, int direction) {
if (DEBUG) Log.d(TAG, "onPipTransition_Finished(), state=" + stateToName(mState));
if (mState == STATE_PIP_MENU) {
if (DEBUG) Log.d(TAG, " > show menu");
mTvPipMenuController.showMenu();
}
}
private void setState(@State int state) {
if (DEBUG) {
Log.d(TAG, "setState(), state=" + stateToName(state) + ", prev="
+ stateToName(mState));
}
mState = state;
}
private void loadConfigurations() {
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
// "Cache" bounds for the Pip menu as "expanded" bounds in PipBoundsState. We'll refer back
// to this value in resizePinnedStack(), when we are adjusting Pip task/window position for
// the menu.
mPipBoundsState.setExpandedBounds(
Rect.unflattenFromString(res.getString(R.string.pip_menu_bounds)));
}
private DisplayInfo getDisplayInfo() {
final DisplayInfo displayInfo = new DisplayInfo();
mContext.getDisplay().getDisplayInfo(displayInfo);
return displayInfo;
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
taskStackListener.addListener(new TaskStackListenerCallback() {
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
checkIfPinnedTaskAppeared();
}
@Override
public void onTaskStackChanged() {
checkIfPinnedTaskIsGone();
}
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
if (task.getWindowingMode() == WINDOWING_MODE_PINNED) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
// If the "Pip-ed" Activity is launched again by Launcher or intent, make it
// fullscreen.
movePipToFullscreen();
}
}
});
}
private void registerWmShellPinnedStackListener(WindowManagerShellWrapper wmShell) {
try {
wmShell.addPinnedStackListener(new PinnedStackListenerForwarder.PinnedStackListener() {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
if (DEBUG) {
Log.d(TAG, "onImeVisibilityChanged(), visible=" + imeVisible
+ ", height=" + imeHeight);
}
if (imeVisible == mPipBoundsState.isImeShowing()
&& (!imeVisible || imeHeight == mPipBoundsState.getImeHeight())) {
// Nothing changed: either IME has been and remains invisible, or remains
// visible with the same height.
return;
}
mPipBoundsState.setImeVisibility(imeVisible, imeHeight);
// "Normal" Pip bounds may have changed, so if we are in the "normal" state,
// let's update the bounds.
if (mState == STATE_PIP) {
resizePinnedStack(STATE_PIP);
}
}
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
if (DEBUG) Log.d(TAG, "onActionsChanged()");
mTvPipMenuController.setAppActions(actions);
}
});
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned stack listener", e);
}
}
private static TaskInfo getPinnedTaskInfo() {
if (DEBUG) Log.d(TAG, "getPinnedTaskInfo()");
try {
final TaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo(
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (DEBUG) Log.d(TAG, " > taskInfo=" + taskInfo);
return taskInfo;
} catch (RemoteException e) {
Log.e(TAG, "getRootTaskInfo() failed", e);
return null;
}
}
private static void removeTask(int taskId) {
if (DEBUG) Log.d(TAG, "removeTask(), taskId=" + taskId);
try {
ActivityTaskManager.getService().removeTask(taskId);
} catch (Exception e) {
Log.e(TAG, "Atm.removeTask() failed", e);
}
}
private static String stateToName(@State int state) {
switch (state) {
case STATE_NO_PIP:
return "NO_PIP";
case STATE_PIP:
return "PIP";
case STATE_PIP_MENU:
return "PIP_MENU";
default:
// This can't happen.
throw new IllegalArgumentException("Unknown state " + state);
}
}
}

View File

@@ -42,7 +42,7 @@ import java.util.List;
*/
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
private static final String TAG = "TvPipMenuController";
private static final boolean DEBUG = PipController.DEBUG;
private static final boolean DEBUG = TvPipController.DEBUG;
private final Context mContext;
private final SystemWindows mSystemWindows;
@@ -134,10 +134,18 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
void hideMenu() {
if (DEBUG) Log.d(TAG, "hideMenu()");
hideMenu(true);
}
if (isMenuVisible()) {
mMenuView.hide();
void hideMenu(boolean movePipWindow) {
if (DEBUG) Log.d(TAG, "hideMenu(), movePipWindow=" + movePipWindow);
if (!isMenuVisible()) {
return;
}
mMenuView.hide();
if (movePipWindow) {
mDelegate.movePipToNormalPosition();
}
}

View File

@@ -53,7 +53,7 @@ import java.util.List;
*/
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private static final String TAG = "TvPipMenuView";
private static final boolean DEBUG = PipController.DEBUG;
private static final boolean DEBUG = TvPipController.DEBUG;
private static final float DISABLED_ACTION_ALPHA = 0.54f;

View File

@@ -44,7 +44,7 @@ import java.util.Objects;
*/
public class TvPipNotificationController {
private static final String TAG = "TvPipNotification";
private static final boolean DEBUG = PipController.DEBUG;
private static final boolean DEBUG = TvPipController.DEBUG;
// Referenced in com.android.systemui.util.NotificationChannels.
public static final String NOTIFICATION_CHANNEL = "TVPIP";

View File

@@ -32,7 +32,7 @@ import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.tv.PipController;
import com.android.wm.shell.pip.tv.TvPipController;
import com.android.wm.shell.pip.tv.TvPipMenuController;
import com.android.wm.shell.pip.tv.TvPipNotificationController;
@@ -59,7 +59,7 @@ public abstract class TvPipModule {
TaskStackListenerImpl taskStackListener,
WindowManagerShellWrapper windowManagerShellWrapper) {
return Optional.of(
new PipController(
new TvPipController(
context,
pipBoundsState,
pipBoundsAlgorithm,