Implement PiP custom close action on phone
To use this new custom close action on Phone - We expect app providing actions via PictureInPictureParams.Builder#setActions as they normally do - We also expect app providing a custom close action via PictureInPictureParams.Builder#setCloseAction - The custom close action is used on Phone only if it matches one of the actions listed in setActions (by comparing the PendingIntent) SysUI will render the custom close action with a highlighted background and ensures the PiP dismisses after certain timeout if the app fails to respond in time. Video: http://recall/-/aaaaaabFQoRHlzixHdtY/fR0hxeorlYrxXyzfZ02btK Bug: 223896879 Test: see video Change-Id: I65c585ae3224061311e476753bc4c72f00c4fa33
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<?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.
|
||||
-->
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid
|
||||
android:color="@color/pip_custom_close_bg" />
|
||||
<size
|
||||
android:width="@dimen/pip_custom_close_bg_size"
|
||||
android:height="@dimen/pip_custom_close_bg_size" />
|
||||
</shape>
|
||||
@@ -21,6 +21,14 @@
|
||||
android:background="?android:selectableItemBackgroundBorderless"
|
||||
android:forceHasOverlappingRendering="false">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/custom_close_bg"
|
||||
android:layout_width="@dimen/pip_custom_close_bg_size"
|
||||
android:layout_height="@dimen/pip_custom_close_bg_size"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/pip_custom_close_bg"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="@dimen/pip_action_inner_size"
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<color name="bubbles_dark">@color/GM2_grey_800</color>
|
||||
<color name="bubbles_icon_tint">@color/GM2_grey_700</color>
|
||||
|
||||
<!-- PiP -->
|
||||
<color name="pip_custom_close_bg">#D93025</color>
|
||||
|
||||
<!-- Compat controls UI -->
|
||||
<color name="compat_controls_background">@android:color/system_neutral1_800</color>
|
||||
<color name="compat_controls_text">@android:color/system_neutral1_50</color>
|
||||
@@ -47,4 +50,4 @@
|
||||
<color name="splash_screen_bg_light">#FFFFFF</color>
|
||||
<color name="splash_screen_bg_dark">#000000</color>
|
||||
<color name="splash_window_background_default">@color/splash_screen_bg_light</color>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -46,6 +46,10 @@
|
||||
<!-- Show PiP enter split icon, which allows apps to directly enter splitscreen from PiP. -->
|
||||
<bool name="config_pipEnableEnterSplitButton">false</bool>
|
||||
|
||||
<!-- 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">1000</integer>
|
||||
|
||||
<!-- Animation duration when using long press on recents to dock -->
|
||||
<integer name="long_press_dock_anim_duration">250</integer>
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
WindowConfiguration#PINNED_WINDOWING_MODE_ELEVATION_IN_DIP -->
|
||||
<dimen name="pip_shadow_radius">5dp</dimen>
|
||||
|
||||
<!-- The width and height of the background for custom action in PiP menu. -->
|
||||
<dimen name="pip_custom_close_bg_size">32dp</dimen>
|
||||
|
||||
<dimen name="dismiss_target_x_size">24dp</dimen>
|
||||
<dimen name="floating_dismiss_bottom_margin">50dp</dimen>
|
||||
|
||||
|
||||
@@ -110,7 +110,10 @@ public class PipUiEventLogger {
|
||||
PICTURE_IN_PICTURE_STASH_RIGHT(711),
|
||||
|
||||
@UiEvent(doc = "User taps on the settings button in PiP menu")
|
||||
PICTURE_IN_PICTURE_SHOW_SETTINGS(933);
|
||||
PICTURE_IN_PICTURE_SHOW_SETTINGS(933),
|
||||
|
||||
@UiEvent(doc = "Closes PiP with app-provided close action")
|
||||
PICTURE_IN_PICTURE_CUSTOM_CLOSE(1058);
|
||||
|
||||
private final int mId;
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ public class PhonePipMenuController implements PipMenuController {
|
||||
private final Optional<SplitScreenController> mSplitScreenController;
|
||||
private final PipUiEventLogger mPipUiEventLogger;
|
||||
private ParceledListSlice<RemoteAction> mAppActions;
|
||||
private RemoteAction mCloseAction;
|
||||
private ParceledListSlice<RemoteAction> mMediaActions;
|
||||
private SyncRtSurfaceTransactionApplier mApplier;
|
||||
private int mMenuState;
|
||||
@@ -459,6 +460,7 @@ public class PhonePipMenuController implements PipMenuController {
|
||||
public void setAppActions(ParceledListSlice<RemoteAction> appActions,
|
||||
RemoteAction closeAction) {
|
||||
mAppActions = appActions;
|
||||
mCloseAction = closeAction;
|
||||
updateMenuActions();
|
||||
}
|
||||
|
||||
@@ -490,9 +492,8 @@ public class PhonePipMenuController implements PipMenuController {
|
||||
private void updateMenuActions() {
|
||||
if (mPipMenuView != null) {
|
||||
final ParceledListSlice<RemoteAction> menuActions = resolveMenuActions();
|
||||
if (menuActions != null) {
|
||||
mPipMenuView.setActions(mPipBoundsState.getBounds(), menuActions.getList());
|
||||
}
|
||||
mPipMenuView.setActions(mPipBoundsState.getBounds(),
|
||||
menuActions == null ? null : menuActions.getList(), mCloseAction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone;
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
@@ -30,6 +31,7 @@ import com.android.wm.shell.R;
|
||||
*/
|
||||
public class PipMenuActionView extends FrameLayout {
|
||||
private ImageView mImageView;
|
||||
private View mCustomCloseBackground;
|
||||
|
||||
public PipMenuActionView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
@@ -39,10 +41,16 @@ public class PipMenuActionView extends FrameLayout {
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mImageView = findViewById(R.id.image);
|
||||
mCustomCloseBackground = findViewById(R.id.custom_close_bg);
|
||||
}
|
||||
|
||||
/** pass through to internal {@link #mImageView} */
|
||||
public void setImageDrawable(Drawable drawable) {
|
||||
mImageView.setImageDrawable(drawable);
|
||||
}
|
||||
|
||||
/** pass through to internal {@link #mCustomCloseBackground} */
|
||||
public void setCustomCloseBackgroundVisibility(@View.Visibility int visibility) {
|
||||
mCustomCloseBackground.setVisibility(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.PendingIntent.CanceledException;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.ComponentName;
|
||||
@@ -72,6 +74,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -113,6 +116,7 @@ public class PipMenuView extends FrameLayout {
|
||||
private boolean mFocusedTaskAllowSplitScreen;
|
||||
|
||||
private final List<RemoteAction> mActions = new ArrayList<>();
|
||||
private RemoteAction mCloseAction;
|
||||
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
private Drawable mBackgroundDrawable;
|
||||
@@ -151,6 +155,9 @@ public class PipMenuView extends FrameLayout {
|
||||
protected View mTopEndContainer;
|
||||
protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm;
|
||||
|
||||
// How long the shell will wait for the app to close the PiP if a custom action is set.
|
||||
private final int mPipForceCloseDelay;
|
||||
|
||||
public PipMenuView(Context context, PhonePipMenuController controller,
|
||||
ShellExecutor mainExecutor, Handler mainHandler,
|
||||
Optional<SplitScreenController> splitScreenController,
|
||||
@@ -166,6 +173,9 @@ public class PipMenuView extends FrameLayout {
|
||||
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
|
||||
inflate(context, R.layout.pip_menu, this);
|
||||
|
||||
mPipForceCloseDelay = context.getResources().getInteger(
|
||||
R.integer.config_pipForceCloseDelay);
|
||||
|
||||
mBackgroundDrawable = mContext.getDrawable(R.drawable.pip_menu_background);
|
||||
mBackgroundDrawable.setAlpha(0);
|
||||
mViewRoot = findViewById(R.id.background);
|
||||
@@ -437,9 +447,13 @@ public class PipMenuView extends FrameLayout {
|
||||
return new Size(width, height);
|
||||
}
|
||||
|
||||
void setActions(Rect stackBounds, List<RemoteAction> actions) {
|
||||
void setActions(Rect stackBounds, @Nullable List<RemoteAction> actions,
|
||||
@Nullable RemoteAction closeAction) {
|
||||
mActions.clear();
|
||||
mActions.addAll(actions);
|
||||
if (actions != null && !actions.isEmpty()) {
|
||||
mActions.addAll(actions);
|
||||
}
|
||||
mCloseAction = closeAction;
|
||||
if (mMenuState == MENU_STATE_FULL) {
|
||||
updateActionViews(mMenuState, stackBounds);
|
||||
}
|
||||
@@ -492,6 +506,8 @@ public class PipMenuView extends FrameLayout {
|
||||
final RemoteAction action = mActions.get(i);
|
||||
final PipMenuActionView actionView =
|
||||
(PipMenuActionView) mActionsGroup.getChildAt(i);
|
||||
final boolean isCloseAction = mCloseAction != null && Objects.equals(
|
||||
mCloseAction.getActionIntent(), action.getActionIntent());
|
||||
|
||||
// TODO: Check if the action drawable has changed before we reload it
|
||||
action.getIcon().loadDrawableAsync(mContext, d -> {
|
||||
@@ -500,16 +516,12 @@ public class PipMenuView extends FrameLayout {
|
||||
actionView.setImageDrawable(d);
|
||||
}
|
||||
}, mMainHandler);
|
||||
actionView.setCustomCloseBackgroundVisibility(
|
||||
isCloseAction ? View.VISIBLE : View.GONE);
|
||||
actionView.setContentDescription(action.getContentDescription());
|
||||
if (action.isEnabled()) {
|
||||
actionView.setOnClickListener(v -> {
|
||||
try {
|
||||
action.getActionIntent().send();
|
||||
} catch (CanceledException e) {
|
||||
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: Failed to send action, %s", TAG, e);
|
||||
}
|
||||
});
|
||||
actionView.setOnClickListener(
|
||||
v -> onActionViewClicked(action.getActionIntent(), isCloseAction));
|
||||
}
|
||||
actionView.setEnabled(action.isEnabled());
|
||||
actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
|
||||
@@ -559,6 +571,32 @@ public class PipMenuView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the {@link PendingIntent} attached to the {@link PipMenuActionView}.
|
||||
* If the given {@link PendingIntent} matches {@link #mCloseAction}, we need to make sure
|
||||
* the PiP is removed after a certain timeout in case the app does not respond in a
|
||||
* timely manner.
|
||||
*/
|
||||
private void onActionViewClicked(@NonNull PendingIntent intent, boolean isCloseAction) {
|
||||
try {
|
||||
intent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
|
||||
"%s: Failed to send action, %s", TAG, e);
|
||||
}
|
||||
if (isCloseAction) {
|
||||
mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_CUSTOM_CLOSE);
|
||||
mAllowTouches = false;
|
||||
mMainExecutor.executeDelayed(() -> {
|
||||
hideMenu();
|
||||
// TODO: it's unsafe to call onPipDismiss with a delay here since
|
||||
// we may have a different PiP by the time this runnable is executed.
|
||||
mController.onPipDismiss();
|
||||
mAllowTouches = true;
|
||||
}, mPipForceCloseDelay);
|
||||
}
|
||||
}
|
||||
|
||||
private void enterSplit() {
|
||||
// Do not notify menu visibility when hiding the menu, the controller will do this when it
|
||||
// handles the message
|
||||
|
||||
Reference in New Issue
Block a user