Merge "Implement custom PiP close action on TV." into tm-dev

This commit is contained in:
Jacqueline Bronger
2022-03-21 07:56:37 +00:00
committed by Android (Google) Code Review
17 changed files with 179 additions and 41 deletions

View File

@@ -47,7 +47,7 @@ oneway interface IPinnedTaskListener {
* Called when the set of actions for the current PiP activity changes, or when the listener
* is first registered to allow the listener to synchronize its state with the controller.
*/
void onActionsChanged(in ParceledListSlice<RemoteAction> actions);
void onActionsChanged(in ParceledListSlice<RemoteAction> actions, in RemoteAction closeAction);
/**
* Called by the window manager to notify the listener that Activity (was or is in pinned mode)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/tv_pip_menu_icon_unfocused" />
</selector>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true"
android:color="@color/tv_pip_menu_close_icon_bg_focused" />
<item android:color="@color/tv_pip_menu_close_icon_bg_unfocused" />
</selector>

View File

@@ -39,4 +39,8 @@
<!-- Duration (in milliseconds) the PiP stays stashed before automatically unstashing. -->
<integer name="config_pipStashDuration">5000</integer>
<!-- Time (duration in milliseconds) that the shell waits for an app to close the PiP by itself
if a custom action is present before closing it. -->
<integer name="config_pipForceCloseDelay">5000</integer>
</resources>

View File

@@ -16,8 +16,10 @@
-->
<resources>
<color name="tv_pip_menu_icon_focused">#0E0E0F</color>
<color name="tv_pip_menu_icon_unfocused">#E8EAED</color>
<color name="tv_pip_menu_icon_unfocused">#F8F9FA</color>
<color name="tv_pip_menu_icon_disabled">#80868B</color>
<color name="tv_pip_menu_close_icon_bg_focused">#D93025</color>
<color name="tv_pip_menu_close_icon_bg_unfocused">#D69F261F</color>
<color name="tv_pip_menu_icon_bg_focused">#E8EAED</color>
<color name="tv_pip_menu_icon_bg_unfocused">#990E0E0F</color>
<color name="tv_pip_menu_focus_border">#E8EAED</color>

View File

@@ -72,9 +72,10 @@ public class PinnedStackListenerForwarder {
}
}
private void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
private void onActionsChanged(ParceledListSlice<RemoteAction> actions,
RemoteAction closeAction) {
for (PinnedTaskListener listener : mListeners) {
listener.onActionsChanged(actions);
listener.onActionsChanged(actions, closeAction);
}
}
@@ -113,9 +114,10 @@ public class PinnedStackListenerForwarder {
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
RemoteAction closeAction) {
mMainExecutor.execute(() -> {
PinnedStackListenerForwarder.this.onActionsChanged(actions);
PinnedStackListenerForwarder.this.onActionsChanged(actions, closeAction);
});
}
@@ -152,7 +154,8 @@ public class PinnedStackListenerForwarder {
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {}
public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
RemoteAction closeAction) {}
public void onActivityHidden(ComponentName componentName) {}

View File

@@ -66,7 +66,7 @@ public interface PipMenuController {
/**
* Given a set of actions, update the menu.
*/
void setAppActions(ParceledListSlice<RemoteAction> appActions);
void setAppActions(ParceledListSlice<RemoteAction> appActions, RemoteAction closeAction);
/**
* Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a

View File

@@ -474,7 +474,8 @@ public class PhonePipMenuController implements PipMenuController {
* Sets the menu actions to the actions provided by the current PiP menu.
*/
@Override
public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
public void setAppActions(ParceledListSlice<RemoteAction> appActions,
RemoteAction closeAction) {
mAppActions = appActions;
updateMenuActions();
}

View File

@@ -246,8 +246,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
mMenuController.setAppActions(actions);
public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
RemoteAction closeAction) {
mMenuController.setAppActions(actions, closeAction);
}
@Override

View File

@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.app.TaskInfo;
import android.content.ComponentName;
@@ -69,7 +70,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private static final int NONEXISTENT_TASK_ID = -1;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "STATE_" }, value = {
@IntDef(prefix = {"STATE_"}, value = {
STATE_NO_PIP,
STATE_PIP,
STATE_PIP_MENU,
@@ -109,6 +110,10 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private int mPinnedTaskId = NONEXISTENT_TASK_ID;
private Runnable mUnstashRunnable;
private RemoteAction mCloseAction;
// How long the shell will wait for the app to close the PiP if a custom action is set.
private int mPipForceCloseDelay;
private int mResizeAnimationDuration;
public static Pip create(
@@ -369,9 +374,29 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public void closePip() {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closePip(), state=%s", TAG, stateToName(mState));
"%s: closePip(), state=%s, loseAction=%s", TAG, stateToName(mState),
mCloseAction);
}
if (mCloseAction != null) {
try {
mCloseAction.getActionIntent().send();
} catch (PendingIntent.CanceledException e) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Failed to send close action, %s", TAG, e);
}
mMainExecutor.executeDelayed(() -> closeCurrentPiP(mPinnedTaskId), mPipForceCloseDelay);
} else {
closeCurrentPiP(mPinnedTaskId);
}
}
private void closeCurrentPiP(int pinnedTaskId) {
if (mPinnedTaskId != pinnedTaskId) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: PiP has already been closed by custom close action", TAG);
return;
}
removeTask(mPinnedTaskId);
onPipDisappeared();
}
@@ -468,6 +493,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private void loadConfigurations() {
final Resources res = mContext.getResources();
mResizeAnimationDuration = res.getInteger(R.integer.config_pipResizeAnimationDuration);
mPipForceCloseDelay = res.getInteger(R.integer.config_pipForceCloseDelay);
}
private void registerTaskStackListenerCallback(TaskStackListenerImpl taskStackListener) {
@@ -592,13 +618,15 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
@Override
public void onActionsChanged(ParceledListSlice<RemoteAction> actions) {
public void onActionsChanged(ParceledListSlice<RemoteAction> actions,
RemoteAction closeAction) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onActionsChanged()", TAG);
}
mTvPipMenuController.setAppActions(actions);
mTvPipMenuController.setAppActions(actions, closeAction);
mCloseAction = closeAction;
}
});
} catch (RemoteException e) {
@@ -617,7 +645,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: > taskInfo=%s", TAG, taskInfo);
"%s: taskInfo=%s", TAG, taskInfo);
}
return taskInfo;
} catch (RemoteException e) {

View File

@@ -126,4 +126,15 @@ public class TvPipMenuActionButton extends RelativeLayout implements View.OnClic
public boolean isEnabled() {
return mButtonView.isEnabled();
}
void setIsCustomCloseAction(boolean isCustomCloseAction) {
mIconImageView.setImageTintList(
getResources().getColorStateList(
isCustomCloseAction ? R.color.tv_pip_menu_close_icon
: R.color.tv_pip_menu_icon));
mButtonView.setBackgroundTintList(getResources()
.getColorStateList(isCustomCloseAction ? R.color.tv_pip_menu_close_icon_bg
: R.color.tv_pip_menu_icon_bg));
}
}

View File

@@ -45,6 +45,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Manages the visibility of the PiP Menu as user interacts with PiP.
@@ -67,6 +68,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private final List<RemoteAction> mMediaActions = new ArrayList<>();
private final List<RemoteAction> mAppActions = new ArrayList<>();
private RemoteAction mCloseAction;
private SyncRtSurfaceTransactionApplier mApplier;
RectF mTmpSourceRectF = new RectF();
@@ -270,12 +272,12 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
public void setAppActions(ParceledListSlice<RemoteAction> actions) {
public void setAppActions(ParceledListSlice<RemoteAction> actions, RemoteAction closeAction) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: setAppActions()", TAG);
}
updateAdditionalActionsList(mAppActions, actions.getList());
updateAdditionalActionsList(mAppActions, actions.getList(), closeAction);
}
private void onMediaActionsChanged(List<RemoteAction> actions) {
@@ -291,17 +293,19 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
enabledActions.add(remoteAction);
}
}
updateAdditionalActionsList(mMediaActions, enabledActions);
updateAdditionalActionsList(mMediaActions, enabledActions, mCloseAction);
}
private void updateAdditionalActionsList(
List<RemoteAction> destination, @Nullable List<RemoteAction> source) {
private void updateAdditionalActionsList(List<RemoteAction> destination,
@Nullable List<RemoteAction> source, RemoteAction closeAction) {
final int number = source != null ? source.size() : 0;
if (number == 0 && destination.isEmpty()) {
if (number == 0 && destination.isEmpty() && Objects.equals(closeAction, mCloseAction)) {
// Nothing changed.
return;
}
mCloseAction = closeAction;
destination.clear();
if (number > 0) {
destination.addAll(source);
@@ -314,9 +318,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
if (!mAppActions.isEmpty()) {
mPipMenuView.setAdditionalActions(mAppActions, mMainHandler);
mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
} else {
mPipMenuView.setAdditionalActions(mMediaActions, mMainHandler);
mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
}
}

View File

@@ -50,6 +50,7 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A View that represents Pip Menu on TV. It's responsible for displaying 3 ever-present Pip Menu
@@ -78,6 +79,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
private Rect mCurrentBounds;
private final TvPipMenuActionButton mExpandButton;
private final TvPipMenuActionButton mCloseButton;
public TvPipMenuView(@NonNull Context context) {
this(context, null);
@@ -100,8 +102,11 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
.setOnClickListener(this);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button)
.setOnClickListener(this);
mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
mCloseButton.setOnClickListener(this);
mCloseButton.setIsCustomCloseAction(true);
mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
.setOnClickListener(this);
mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
@@ -220,12 +225,24 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|| mArrowLeft.getAlpha() != 0f;
}
void setAdditionalActions(List<RemoteAction> actions, Handler mainHandler) {
void setAdditionalActions(List<RemoteAction> actions, RemoteAction closeAction,
Handler mainHandler) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: setAdditionalActions()", TAG);
}
// Replace system close action with custom close action if available
if (closeAction != null) {
setActionForButton(closeAction, mCloseButton, mainHandler);
} else {
mCloseButton.setTextAndDescription(R.string.pip_close);
mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
}
mCloseButton.setIsCustomCloseAction(closeAction != null);
// Make sure the close action is always enabled
mCloseButton.setEnabled(true);
// Make sure we exactly as many additional buttons as we have actions to display.
final int actionsNumber = actions.size();
int buttonsNumber = mAdditionalButtons.size();
@@ -256,14 +273,37 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
for (int index = 0; index < actionsNumber; index++) {
final RemoteAction action = actions.get(index);
final TvPipMenuActionButton button = mAdditionalButtons.get(index);
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
button.setTextAndDescription(action.getContentDescription());
button.setEnabled(action.isEnabled());
button.setTag(action);
action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
// Remove action if it matches the custom close action.
if (actionsMatch(action, closeAction)) {
button.setVisibility(GONE);
continue;
}
setActionForButton(action, button, mainHandler);
}
}
/**
* Checks whether title, description and intent match.
* Comparing icons would be good, but using equals causes false negatives
*/
private boolean actionsMatch(RemoteAction action1, RemoteAction action2) {
if (action1 == action2) return true;
if (action1 == null) return false;
return Objects.equals(action1.getTitle(), action2.getTitle())
&& Objects.equals(action1.getContentDescription(), action2.getContentDescription())
&& Objects.equals(action1.getActionIntent(), action2.getActionIntent());
}
private void setActionForButton(RemoteAction action, TvPipMenuActionButton button,
Handler mainHandler) {
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
button.setTextAndDescription(action.getContentDescription());
button.setEnabled(action.isEnabled());
button.setTag(action);
action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
}
@Nullable
SurfaceControl getWindowSurfaceControl() {
final ViewRootImpl root = getViewRootImpl();

View File

@@ -749,7 +749,8 @@ class ActivityClientController extends IActivityClientController.Stub {
rootTask.setPictureInPictureAspectRatio(
r.pictureInPictureArgs.getAspectRatioFloat(),
r.pictureInPictureArgs.getExpandedAspectRatioFloat());
rootTask.setPictureInPictureActions(r.pictureInPictureArgs.getActions());
rootTask.setPictureInPictureActions(r.pictureInPictureArgs.getActions(),
r.pictureInPictureArgs.getCloseAction());
}
}
} finally {

View File

@@ -3527,11 +3527,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final float expandedAspectRatio =
r.pictureInPictureArgs.getExpandedAspectRatioFloat();
final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
final RemoteAction closeAction = r.pictureInPictureArgs.getCloseAction();
mRootWindowContainer.moveActivityToPinnedRootTask(r,
null /* launchIntoPipHostActivity */, "enterPictureInPictureMode");
final Task task = r.getTask();
task.setPictureInPictureAspectRatio(aspectRatio, expandedAspectRatio);
task.setPictureInPictureActions(actions);
task.setPictureInPictureActions(actions, closeAction);
// Continue the pausing process after entering pip.
if (task.getPausingActivity() == r) {

View File

@@ -92,6 +92,7 @@ class PinnedTaskController {
// The set of actions and aspect-ratio for the that are currently allowed on the PiP activity
private ArrayList<RemoteAction> mActions = new ArrayList<>();
private RemoteAction mCloseAction;
private float mAspectRatio = -1f;
private float mExpandedAspectRatio = 0f;
@@ -154,7 +155,7 @@ class PinnedTaskController {
mPinnedTaskListener = listener;
notifyImeVisibilityChanged(mIsImeShowing, mImeHeight);
notifyMovementBoundsChanged(false /* fromImeAdjustment */);
notifyActionsChanged(mActions);
notifyActionsChanged(mActions, mCloseAction);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register pinned task listener", e);
}
@@ -408,12 +409,13 @@ class PinnedTaskController {
/**
* Sets the current set of actions.
*/
void setActions(List<RemoteAction> actions) {
void setActions(List<RemoteAction> actions, RemoteAction closeAction) {
mActions.clear();
if (actions != null) {
mActions.addAll(actions);
}
notifyActionsChanged(mActions);
mCloseAction = closeAction;
notifyActionsChanged(mActions, closeAction);
}
/**
@@ -450,10 +452,10 @@ class PinnedTaskController {
/**
* Notifies listeners that the PIP actions have changed.
*/
private void notifyActionsChanged(List<RemoteAction> actions) {
private void notifyActionsChanged(List<RemoteAction> actions, RemoteAction closeAction) {
if (mPinnedTaskListener != null) {
try {
mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions));
mPinnedTaskListener.onActionsChanged(new ParceledListSlice(actions), closeAction);
} catch (RemoteException e) {
Slog.e(TAG_WM, "Error delivering actions changed event.", e);
}

View File

@@ -6098,7 +6098,7 @@ class Task extends TaskFragment {
/**
* Sets the current picture-in-picture actions.
*/
void setPictureInPictureActions(List<RemoteAction> actions) {
void setPictureInPictureActions(List<RemoteAction> actions, RemoteAction closeAction) {
if (!mWmService.mAtmService.mSupportsPictureInPicture) {
return;
}
@@ -6107,7 +6107,7 @@ class Task extends TaskFragment {
return;
}
getDisplayContent().getPinnedTaskController().setActions(actions);
getDisplayContent().getPinnedTaskController().setActions(actions, closeAction);
}
public DisplayInfo getDisplayInfo() {