Merge "Fix and clean up Tv Pip Menu"
This commit is contained in:
committed by
Android (Google) Code Review
commit
6581a7d0ac
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlsView}. -->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<com.android.wm.shell.pip.tv.PipControlButtonView
|
||||
android:id="@+id/full_button"
|
||||
android:layout_width="@dimen/picture_in_picture_button_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/pip_ic_fullscreen_white"
|
||||
android:text="@string/pip_fullscreen" />
|
||||
|
||||
<com.android.wm.shell.pip.tv.PipControlButtonView
|
||||
android:id="@+id/close_button"
|
||||
android:layout_width="@dimen/picture_in_picture_button_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
|
||||
android:src="@drawable/pip_ic_close_white"
|
||||
android:text="@string/pip_close" />
|
||||
</merge>
|
||||
@@ -14,19 +14,39 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tv_pip_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="350dp"
|
||||
android:background="#CC000000"
|
||||
android:gravity="top|center_horizontal"
|
||||
android:clipChildren="false">
|
||||
<!-- Layout for TvPipMenuView -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tv_pip_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#CC000000">
|
||||
|
||||
<com.android.wm.shell.pip.tv.PipControlsView
|
||||
android:id="@+id/pip_controls"
|
||||
<LinearLayout
|
||||
android:id="@+id/tv_pip_menu_action_buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:alpha="0" />
|
||||
</LinearLayout>
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="350dp"
|
||||
android:orientation="horizontal"
|
||||
android:alpha="0">
|
||||
|
||||
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
|
||||
android:id="@+id/tv_pip_menu_fullscreen_button"
|
||||
android:layout_width="@dimen/picture_in_picture_button_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/pip_ic_fullscreen_white"
|
||||
android:text="@string/pip_fullscreen" />
|
||||
|
||||
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
|
||||
android:id="@+id/tv_pip_menu_close_button"
|
||||
android:layout_width="@dimen/picture_in_picture_button_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
|
||||
android:src="@drawable/pip_ic_close_white"
|
||||
android:text="@string/pip_close" />
|
||||
|
||||
<!-- More TvPipMenuActionButtons may be added here at runtime. -->
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Layout for {@link com.android.wm.shell.pip.tv.PipControlButtonView}. -->
|
||||
<!-- Layout for TvPipMenuActionButton -->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<ImageView android:id="@+id/button"
|
||||
@@ -14,7 +14,7 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<com.android.wm.shell.pip.tv.PipControlButtonView
|
||||
<com.android.wm.shell.pip.tv.TvPipMenuActionButton
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="@dimen/picture_in_picture_button_width"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -60,7 +60,8 @@ import java.util.Objects;
|
||||
/**
|
||||
* Manages the picture-in-picture (PIP) UI and states.
|
||||
*/
|
||||
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback {
|
||||
public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallback,
|
||||
TvPipMenuController.Delegate {
|
||||
private static final String TAG = "TvPipController";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
@@ -198,7 +199,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
mPipBoundsAlgorithm = pipBoundsAlgorithm;
|
||||
mPipMediaController = pipMediaController;
|
||||
mTvPipMenuController = tvPipMenuController;
|
||||
mTvPipMenuController.attachPipController(this);
|
||||
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();
|
||||
@@ -289,6 +290,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
/**
|
||||
* Closes PIP (PIPed activity and PIP system UI).
|
||||
*/
|
||||
@Override
|
||||
public void closePip() {
|
||||
if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
|
||||
|
||||
@@ -318,9 +320,15 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac
|
||||
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());
|
||||
|
||||
|
||||
@@ -1,61 +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 android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
|
||||
/**
|
||||
* A view containing PIP controls including fullscreen, close, and media controls.
|
||||
*/
|
||||
public class PipControlsView extends LinearLayout {
|
||||
|
||||
public PipControlsView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public PipControlsView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
|
||||
Context.LAYOUT_INFLATER_SERVICE);
|
||||
layoutInflater.inflate(R.layout.tv_pip_controls, this);
|
||||
setOrientation(LinearLayout.HORIZONTAL);
|
||||
setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
|
||||
}
|
||||
|
||||
PipControlButtonView getFullscreenButton() {
|
||||
return findViewById(R.id.full_button);
|
||||
}
|
||||
|
||||
PipControlButtonView getCloseButton() {
|
||||
return findViewById(R.id.close_button);
|
||||
}
|
||||
}
|
||||
@@ -1,158 +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 android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* Controller for {@link PipControlsView}.
|
||||
*/
|
||||
public class PipControlsViewController {
|
||||
private static final String TAG = PipControlsViewController.class.getSimpleName();
|
||||
|
||||
private static final float DISABLED_ACTION_ALPHA = 0.54f;
|
||||
|
||||
private final PipController mPipController;
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mUiThreadHandler;
|
||||
private final PipControlsView mView;
|
||||
private final List<PipControlButtonView> mAdditionalButtons = new ArrayList<>();
|
||||
|
||||
private final List<RemoteAction> mCustomActions = new ArrayList<>();
|
||||
private final List<RemoteAction> mMediaActions = new ArrayList<>();
|
||||
|
||||
public PipControlsViewController(PipControlsView view, PipController pipController) {
|
||||
mContext = view.getContext();
|
||||
mUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||
mPipController = pipController;
|
||||
mView = view;
|
||||
|
||||
mView.getFullscreenButton().setOnClickListener(v -> mPipController.movePipToFullscreen());
|
||||
mView.getCloseButton().setOnClickListener(v -> mPipController.closePip());
|
||||
|
||||
mPipController.getPipMediaController().addActionListener(this::onMediaActionsChanged);
|
||||
}
|
||||
|
||||
PipControlsView getView() {
|
||||
return mView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the set of activity-defined actions.
|
||||
*/
|
||||
void setCustomActions(List<? extends RemoteAction> actions) {
|
||||
if (mCustomActions.isEmpty() && actions.isEmpty()) {
|
||||
// Nothing changed - return early.
|
||||
return;
|
||||
}
|
||||
mCustomActions.clear();
|
||||
mCustomActions.addAll(actions);
|
||||
updateAdditionalActions();
|
||||
}
|
||||
|
||||
private void onMediaActionsChanged(List<RemoteAction> actions) {
|
||||
if (mMediaActions.isEmpty() && actions.isEmpty()) {
|
||||
// Nothing changed - return early.
|
||||
return;
|
||||
}
|
||||
mMediaActions.clear();
|
||||
mMediaActions.addAll(actions);
|
||||
|
||||
// Update the view only if there are no custom actions (media actions are only shown when
|
||||
// there no custom actions).
|
||||
if (mCustomActions.isEmpty()) {
|
||||
updateAdditionalActions();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAdditionalActions() {
|
||||
final List<RemoteAction> actionsToDisplay;
|
||||
if (!mCustomActions.isEmpty()) {
|
||||
// If there are custom actions: show them.
|
||||
actionsToDisplay = mCustomActions;
|
||||
} else if (!mMediaActions.isEmpty()) {
|
||||
// If there are no custom actions, but there media actions: show them.
|
||||
actionsToDisplay = mMediaActions;
|
||||
} else {
|
||||
// If there no custom actions and no media actions: clean up all the additional buttons.
|
||||
actionsToDisplay = Collections.emptyList();
|
||||
}
|
||||
|
||||
// Make sure we exactly as many additional buttons as we have actions to display.
|
||||
final int actionsNumber = actionsToDisplay.size();
|
||||
int buttonsNumber = mAdditionalButtons.size();
|
||||
if (actionsNumber > buttonsNumber) {
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
|
||||
// Add buttons until we have enough to display all of the actions.
|
||||
while (actionsNumber > buttonsNumber) {
|
||||
final PipControlButtonView button = (PipControlButtonView) layoutInflater.inflate(
|
||||
R.layout.tv_pip_custom_control, mView, false);
|
||||
mView.addView(button);
|
||||
mAdditionalButtons.add(button);
|
||||
|
||||
buttonsNumber++;
|
||||
}
|
||||
} else if (actionsNumber < buttonsNumber) {
|
||||
// Hide buttons until we as many as the actions.
|
||||
while (actionsNumber < buttonsNumber) {
|
||||
final View button = mAdditionalButtons.get(buttonsNumber - 1);
|
||||
button.setVisibility(View.GONE);
|
||||
button.setOnClickListener(null);
|
||||
|
||||
buttonsNumber--;
|
||||
}
|
||||
}
|
||||
|
||||
// "Assign" actions to the buttons.
|
||||
for (int index = 0; index < actionsNumber; index++) {
|
||||
final RemoteAction action = actionsToDisplay.get(index);
|
||||
final PipControlButtonView button = mAdditionalButtons.get(index);
|
||||
button.setVisibility(View.VISIBLE); // Ensure the button is visible.
|
||||
button.setText(action.getContentDescription());
|
||||
button.setEnabled(action.isEnabled());
|
||||
button.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
action.getActionIntent().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.w(TAG, "Failed to send action", e);
|
||||
}
|
||||
});
|
||||
|
||||
action.getIcon().loadDrawableAsync(mContext, drawable -> {
|
||||
drawable.setTint(Color.WHITE);
|
||||
button.setImageDrawable(drawable);
|
||||
}, mUiThreadHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,126 +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.view.KeyEvent.ACTION_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* The Menu View that shows controls of the PiP. Always fullscreen.
|
||||
*/
|
||||
public class PipMenuView extends FrameLayout {
|
||||
private static final String TAG = "PipMenuView";
|
||||
private static final boolean DEBUG = PipController.DEBUG;
|
||||
|
||||
private final Animator mFadeInAnimation;
|
||||
private final Animator mFadeOutAnimation;
|
||||
private final PipControlsViewController mPipControlsViewController;
|
||||
@Nullable
|
||||
private OnBackPressListener mOnBackPressListener;
|
||||
|
||||
public PipMenuView(Context context, PipController pipController) {
|
||||
super(context, null, 0);
|
||||
inflate(context, R.layout.tv_pip_menu, this);
|
||||
|
||||
mPipControlsViewController = new PipControlsViewController(
|
||||
findViewById(R.id.pip_controls), pipController);
|
||||
mFadeInAnimation = AnimatorInflater.loadAnimator(
|
||||
mContext, R.anim.tv_pip_menu_fade_in_animation);
|
||||
mFadeInAnimation.setTarget(mPipControlsViewController.getView());
|
||||
mFadeOutAnimation = AnimatorInflater.loadAnimator(
|
||||
mContext, R.anim.tv_pip_menu_fade_out_animation);
|
||||
mFadeOutAnimation.setTarget(mPipControlsViewController.getView());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SurfaceControl getWindowSurfaceControl() {
|
||||
final ViewRootImpl root = getViewRootImpl();
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
final SurfaceControl out = root.getSurfaceControl();
|
||||
if (out != null && out.isValid()) {
|
||||
return out;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void showMenu() {
|
||||
mFadeInAnimation.start();
|
||||
setAlpha(1.0f);
|
||||
grantWindowFocus(true);
|
||||
}
|
||||
|
||||
void hideMenu() {
|
||||
mFadeOutAnimation.start();
|
||||
setAlpha(0.0f);
|
||||
grantWindowFocus(false);
|
||||
}
|
||||
|
||||
private void grantWindowFocus(boolean grantFocus) {
|
||||
try {
|
||||
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
|
||||
getViewRootImpl().getInputToken(), grantFocus);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to update focus as menu disappears", e);
|
||||
}
|
||||
}
|
||||
|
||||
void setOnBackPressListener(OnBackPressListener onBackPressListener) {
|
||||
mOnBackPressListener = onBackPressListener;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
void setAppActions(ParceledListSlice<RemoteAction> actions) {
|
||||
if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
|
||||
|
||||
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
|
||||
mPipControlsViewController.setCustomActions(
|
||||
hasCustomActions ? actions.getList() : Collections.emptyList());
|
||||
}
|
||||
|
||||
interface OnBackPressListener {
|
||||
void onBackPress();
|
||||
}
|
||||
}
|
||||
@@ -31,64 +31,51 @@ import android.widget.TextView;
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
/**
|
||||
* A view containing PIP controls including fullscreen, close, and media controls.
|
||||
* A View that represents Pip Menu action button, such as "Fullscreen" and "Close" as well custom
|
||||
* (provided by the application in Pip) and media buttons.
|
||||
*/
|
||||
public class PipControlButtonView extends RelativeLayout {
|
||||
|
||||
private OnFocusChangeListener mFocusChangeListener;
|
||||
private ImageView mIconImageView;
|
||||
ImageView mButtonImageView;
|
||||
private TextView mDescriptionTextView;
|
||||
public class TvPipMenuActionButton extends RelativeLayout implements View.OnClickListener {
|
||||
private final ImageView mIconImageView;
|
||||
private final ImageView mButtonImageView;
|
||||
private final TextView mDescriptionTextView;
|
||||
private Animator mTextFocusGainAnimator;
|
||||
private Animator mButtonFocusGainAnimator;
|
||||
private Animator mTextFocusLossAnimator;
|
||||
private Animator mButtonFocusLossAnimator;
|
||||
private OnClickListener mOnClickListener;
|
||||
|
||||
private final OnFocusChangeListener mInternalFocusChangeListener =
|
||||
new OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
if (hasFocus) {
|
||||
startFocusGainAnimation();
|
||||
} else {
|
||||
startFocusLossAnimation();
|
||||
}
|
||||
|
||||
if (mFocusChangeListener != null) {
|
||||
mFocusChangeListener.onFocusChange(PipControlButtonView.this, hasFocus);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public PipControlButtonView(Context context) {
|
||||
public TvPipMenuActionButton(Context context) {
|
||||
this(context, null, 0, 0);
|
||||
}
|
||||
|
||||
public PipControlButtonView(Context context, AttributeSet attrs) {
|
||||
public TvPipMenuActionButton(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0, 0);
|
||||
}
|
||||
|
||||
public PipControlButtonView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
public TvPipMenuActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public PipControlButtonView(
|
||||
public TvPipMenuActionButton(
|
||||
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
final LayoutInflater inflater = (LayoutInflater) getContext()
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater.inflate(R.layout.tv_pip_control_button, this);
|
||||
inflater.inflate(R.layout.tv_pip_menu_action_button, this);
|
||||
|
||||
mIconImageView = findViewById(R.id.icon);
|
||||
mButtonImageView = findViewById(R.id.button);
|
||||
mDescriptionTextView = findViewById(R.id.desc);
|
||||
|
||||
int[] values = new int[]{android.R.attr.src, android.R.attr.text};
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
|
||||
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
|
||||
final TypedArray typedArray = context.obtainStyledAttributes(attrs, values, defStyleAttr,
|
||||
defStyleRes);
|
||||
|
||||
setImageResource(typedArray.getResourceId(0, 0));
|
||||
setText(typedArray.getResourceId(1, 0));
|
||||
final int textResId = typedArray.getResourceId(1, 0);
|
||||
if (textResId != 0) {
|
||||
setTextAndDescription(getContext().getString(textResId));
|
||||
}
|
||||
|
||||
typedArray.recycle();
|
||||
}
|
||||
@@ -96,7 +83,13 @@ public class PipControlButtonView extends RelativeLayout {
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mButtonImageView.setOnFocusChangeListener(mInternalFocusChangeListener);
|
||||
mButtonImageView.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
startFocusGainAnimation();
|
||||
} else {
|
||||
startFocusLossAnimation();
|
||||
}
|
||||
});
|
||||
|
||||
mTextFocusGainAnimator = AnimatorInflater.loadAnimator(getContext(),
|
||||
R.anim.tv_pip_controls_focus_gain_animation);
|
||||
@@ -115,12 +108,19 @@ public class PipControlButtonView extends RelativeLayout {
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener listener) {
|
||||
mButtonImageView.setOnClickListener(listener);
|
||||
// We do not want to set an OnClickListener to the TvPipMenuActionButton itself, but only to
|
||||
// the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
|
||||
// listener to the ImageView.
|
||||
mOnClickListener = listener;
|
||||
mButtonImageView.setOnClickListener(listener != null ? this : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnFocusChangeListener(OnFocusChangeListener listener) {
|
||||
mFocusChangeListener = listener;
|
||||
public void onClick(View v) {
|
||||
if (mOnClickListener != null) {
|
||||
// Pass the correct view - this.
|
||||
mOnClickListener.onClick(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,21 +142,11 @@ public class PipControlButtonView extends RelativeLayout {
|
||||
/**
|
||||
* Sets the text for description the with the given string.
|
||||
*/
|
||||
public void setText(CharSequence text) {
|
||||
public void setTextAndDescription(CharSequence text) {
|
||||
mButtonImageView.setContentDescription(text);
|
||||
mDescriptionTextView.setText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text for description the with the given resource id.
|
||||
*/
|
||||
public void setText(int resId) {
|
||||
if (resId != 0) {
|
||||
mButtonImageView.setContentDescription(getContext().getString(resId));
|
||||
mDescriptionTextView.setText(resId);
|
||||
}
|
||||
}
|
||||
|
||||
private static void cancelAnimator(Animator animator) {
|
||||
if (animator.isStarted()) {
|
||||
animator.cancel();
|
||||
@@ -187,8 +177,8 @@ public class PipControlButtonView extends RelativeLayout {
|
||||
mTextFocusLossAnimator.start();
|
||||
if (mButtonImageView.hasFocus()) {
|
||||
// Button uses ripple that has the default animation for the focus changes.
|
||||
// Howevever, it doesn't expose the API to fade out while it is focused,
|
||||
// so we should manually run the fade out animation when PIP controls row loses focus.
|
||||
// However, it doesn't expose the API to fade out while it is focused, so we should
|
||||
// manually run the fade out animation when PIP controls row loses focus.
|
||||
mButtonFocusLossAnimator.start();
|
||||
}
|
||||
}
|
||||
@@ -19,38 +19,97 @@ package com.android.wm.shell.pip.tv;
|
||||
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
|
||||
|
||||
import android.app.RemoteAction;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.util.Log;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.wm.shell.common.SystemWindows;
|
||||
import com.android.wm.shell.pip.PipBoundsState;
|
||||
import com.android.wm.shell.pip.PipMediaController;
|
||||
import com.android.wm.shell.pip.PipMenuController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Manages the visibility of the PiP Menu as user interacts with PiP.
|
||||
*/
|
||||
public class TvPipMenuController implements PipMenuController {
|
||||
public class TvPipMenuController implements PipMenuController, TvPipMenuView.Listener {
|
||||
private static final String TAG = "TvPipMenuController";
|
||||
private static final boolean DEBUG = PipController.DEBUG;
|
||||
|
||||
private final Context mContext;
|
||||
private final SystemWindows mSystemWindows;
|
||||
private final PipBoundsState mPipBoundsState;
|
||||
private PipMenuView mMenuView;
|
||||
private PipController mPipController;
|
||||
|
||||
private Delegate mDelegate;
|
||||
private SurfaceControl mLeash;
|
||||
private TvPipMenuView mMenuView;
|
||||
|
||||
private final List<RemoteAction> mMediaActions = new ArrayList<>();
|
||||
private final List<RemoteAction> mAppActions = new ArrayList<>();
|
||||
|
||||
public TvPipMenuController(Context context, PipBoundsState pipBoundsState,
|
||||
SystemWindows systemWindows) {
|
||||
SystemWindows systemWindows, PipMediaController pipMediaController) {
|
||||
mContext = context;
|
||||
mPipBoundsState = pipBoundsState;
|
||||
mSystemWindows = systemWindows;
|
||||
|
||||
// We need to "close" the menu the platform call for all the system dialogs to close (for
|
||||
// example, on the Home button press).
|
||||
final BroadcastReceiver closeSystemDialogsBroadcastReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
hideMenu();
|
||||
}
|
||||
};
|
||||
context.registerReceiver(closeSystemDialogsBroadcastReceiver,
|
||||
new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
|
||||
|
||||
pipMediaController.addActionListener(this::onMediaActionsChanged);
|
||||
}
|
||||
|
||||
void attachPipController(PipController pipController) {
|
||||
mPipController = pipController;
|
||||
void setDelegate(Delegate delegate) {
|
||||
if (DEBUG) Log.d(TAG, "setDelegate(), delegate=" + delegate);
|
||||
if (mDelegate != null) {
|
||||
throw new IllegalStateException(
|
||||
"The delegate has already been set and should not change.");
|
||||
}
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("The delegate must not be null.");
|
||||
}
|
||||
|
||||
mDelegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(SurfaceControl leash) {
|
||||
if (mDelegate == null) {
|
||||
throw new IllegalStateException("Delegate is not set.");
|
||||
}
|
||||
|
||||
mLeash = leash;
|
||||
attachPipMenuView();
|
||||
}
|
||||
|
||||
private void attachPipMenuView() {
|
||||
if (DEBUG) Log.d(TAG, "attachPipMenuView()");
|
||||
|
||||
if (mMenuView != null) {
|
||||
detachPipMenuView();
|
||||
}
|
||||
|
||||
mMenuView = new TvPipMenuView(mContext);
|
||||
mMenuView.setListener(this);
|
||||
mSystemWindows.addView(mMenuView,
|
||||
getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
|
||||
0, SHELL_ROOT_LAYER_PIP);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +120,8 @@ public class TvPipMenuController implements PipMenuController {
|
||||
mSystemWindows.updateViewLayout(mMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE,
|
||||
mPipBoundsState.getDisplayBounds().width(),
|
||||
mPipBoundsState.getDisplayBounds().height()));
|
||||
mMenuView.showMenu();
|
||||
maybeUpdateMenuViewActions();
|
||||
mMenuView.show();
|
||||
|
||||
// By default, SystemWindows views are above everything else.
|
||||
// Set the relative z-order so the menu is below PiP.
|
||||
@@ -77,17 +137,11 @@ public class TvPipMenuController implements PipMenuController {
|
||||
if (DEBUG) Log.d(TAG, "hideMenu()");
|
||||
|
||||
if (isMenuVisible()) {
|
||||
mMenuView.hideMenu();
|
||||
mPipController.resizePinnedStack(PipController.STATE_PIP);
|
||||
mMenuView.hide();
|
||||
mDelegate.movePipToNormalPosition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attach(SurfaceControl leash) {
|
||||
mLeash = leash;
|
||||
attachPipMenuView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detach() {
|
||||
hideMenu();
|
||||
@@ -95,20 +149,6 @@ public class TvPipMenuController implements PipMenuController {
|
||||
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()");
|
||||
|
||||
@@ -121,18 +161,65 @@ public class TvPipMenuController implements PipMenuController {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppActions(ParceledListSlice<RemoteAction> appActions) {
|
||||
if (DEBUG) Log.d(TAG, "setAppActions(), actions=" + appActions);
|
||||
public void setAppActions(ParceledListSlice<RemoteAction> actions) {
|
||||
if (DEBUG) Log.d(TAG, "setAppActions()");
|
||||
updateAdditionalActionsList(mAppActions, actions.getList());
|
||||
}
|
||||
|
||||
if (mMenuView != null) {
|
||||
mMenuView.setAppActions(appActions);
|
||||
private void onMediaActionsChanged(List<RemoteAction> actions) {
|
||||
if (DEBUG) Log.d(TAG, "onMediaActionsChanged()");
|
||||
updateAdditionalActionsList(mMediaActions, actions);
|
||||
}
|
||||
|
||||
private void updateAdditionalActionsList(
|
||||
List<RemoteAction> destination, @Nullable List<RemoteAction> source) {
|
||||
final int number = source != null ? source.size() : 0;
|
||||
if (number == 0 && destination.isEmpty()) {
|
||||
// Nothing changed.
|
||||
return;
|
||||
}
|
||||
|
||||
destination.clear();
|
||||
if (number > 0) {
|
||||
destination.addAll(source);
|
||||
}
|
||||
maybeUpdateMenuViewActions();
|
||||
}
|
||||
|
||||
private void maybeUpdateMenuViewActions() {
|
||||
if (mMenuView == null) {
|
||||
return;
|
||||
}
|
||||
if (!mAppActions.isEmpty()) {
|
||||
mMenuView.setAdditionalActions(mAppActions);
|
||||
} else {
|
||||
Log.w(TAG, "Cannot set remote actions, there is no View");
|
||||
mMenuView.setAdditionalActions(mMediaActions);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMenuVisible() {
|
||||
return mMenuView != null && mMenuView.getAlpha() == 1.0f;
|
||||
return mMenuView != null && mMenuView.isVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPress() {
|
||||
hideMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCloseButtonClick() {
|
||||
mDelegate.closePip();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFullscreenButtonClick() {
|
||||
mDelegate.movePipToFullscreen();
|
||||
}
|
||||
|
||||
interface Delegate {
|
||||
void movePipToNormalPosition();
|
||||
void movePipToFullscreen();
|
||||
void closePip();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.animation.AnimatorInflater.loadAnimator;
|
||||
import static android.view.KeyEvent.ACTION_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.RemoteAction;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A View that represents Pip Menu on TV. It's responsible for displaying 2 ever-present Pip Menu
|
||||
* actions: Fullscreen and Close, but could also display "additional" actions, that may be set via
|
||||
* a {@link #setAdditionalActions(List)} call.
|
||||
*/
|
||||
public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
|
||||
private static final String TAG = "TvPipMenuView";
|
||||
private static final boolean DEBUG = PipController.DEBUG;
|
||||
|
||||
private static final float DISABLED_ACTION_ALPHA = 0.54f;
|
||||
|
||||
private final Handler mUiThreadHandler;
|
||||
private final Animator mFadeInAnimation;
|
||||
private final Animator mFadeOutAnimation;
|
||||
@Nullable private Listener mListener;
|
||||
|
||||
private final LinearLayout mActionButtonsContainer;
|
||||
private final List<TvPipMenuActionButton> mAdditionalButtons = new ArrayList<>();
|
||||
|
||||
public TvPipMenuView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public TvPipMenuView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mUiThreadHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
inflate(context, R.layout.tv_pip_menu, this);
|
||||
|
||||
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);
|
||||
|
||||
mFadeInAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_in_animation);
|
||||
mFadeInAnimation.setTarget(mActionButtonsContainer);
|
||||
|
||||
mFadeOutAnimation = loadAnimator(mContext, R.anim.tv_pip_menu_fade_out_animation);
|
||||
mFadeOutAnimation.setTarget(mActionButtonsContainer);
|
||||
}
|
||||
|
||||
void setListener(@Nullable Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
void show() {
|
||||
if (DEBUG) Log.d(TAG, "show()");
|
||||
|
||||
mFadeInAnimation.start();
|
||||
setAlpha(1.0f);
|
||||
grantWindowFocus(true);
|
||||
}
|
||||
|
||||
void hide() {
|
||||
if (DEBUG) Log.d(TAG, "hide()");
|
||||
|
||||
mFadeOutAnimation.start();
|
||||
setAlpha(0.0f);
|
||||
grantWindowFocus(false);
|
||||
}
|
||||
|
||||
boolean isVisible() {
|
||||
return getAlpha() == 1.0f;
|
||||
}
|
||||
|
||||
private void grantWindowFocus(boolean grantFocus) {
|
||||
if (DEBUG) Log.d(TAG, "grantWindowFocus(" + grantFocus + ")");
|
||||
|
||||
try {
|
||||
WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
|
||||
getViewRootImpl().getInputToken(), grantFocus);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to update focus", e);
|
||||
}
|
||||
}
|
||||
|
||||
void setAdditionalActions(List<RemoteAction> actions) {
|
||||
if (DEBUG) Log.d(TAG, "setAdditionalActions()");
|
||||
|
||||
// Make sure we exactly as many additional buttons as we have actions to display.
|
||||
final int actionsNumber = actions.size();
|
||||
int buttonsNumber = mAdditionalButtons.size();
|
||||
if (actionsNumber > buttonsNumber) {
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
|
||||
// Add buttons until we have enough to display all of the actions.
|
||||
while (actionsNumber > buttonsNumber) {
|
||||
final TvPipMenuActionButton button = (TvPipMenuActionButton) layoutInflater.inflate(
|
||||
R.layout.tv_pip_menu_additional_action_button, mActionButtonsContainer,
|
||||
false);
|
||||
button.setOnClickListener(this);
|
||||
|
||||
mActionButtonsContainer.addView(button);
|
||||
mAdditionalButtons.add(button);
|
||||
|
||||
buttonsNumber++;
|
||||
}
|
||||
} else if (actionsNumber < buttonsNumber) {
|
||||
// Hide buttons until we as many as the actions.
|
||||
while (actionsNumber < buttonsNumber) {
|
||||
final View button = mAdditionalButtons.get(buttonsNumber - 1);
|
||||
button.setVisibility(View.GONE);
|
||||
button.setTag(null);
|
||||
|
||||
buttonsNumber--;
|
||||
}
|
||||
}
|
||||
|
||||
// "Assign" actions to the buttons.
|
||||
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.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
|
||||
button.setTag(action);
|
||||
|
||||
action.getIcon().loadDrawableAsync(mContext, drawable -> {
|
||||
drawable.setTint(Color.WHITE);
|
||||
button.setImageDrawable(drawable);
|
||||
}, mUiThreadHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
SurfaceControl getWindowSurfaceControl() {
|
||||
final ViewRootImpl root = getViewRootImpl();
|
||||
if (root == null) {
|
||||
return null;
|
||||
}
|
||||
final SurfaceControl out = root.getSurfaceControl();
|
||||
if (out != null && out.isValid()) {
|
||||
return out;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mListener == null) return;
|
||||
|
||||
final int id = v.getId();
|
||||
if (id == R.id.tv_pip_menu_fullscreen_button) {
|
||||
mListener.onFullscreenButtonClick();
|
||||
} else if (id == R.id.tv_pip_menu_close_button) {
|
||||
mListener.onCloseButtonClick();
|
||||
} else {
|
||||
// This should be an "additional action"
|
||||
final RemoteAction action = (RemoteAction) v.getTag();
|
||||
if (action != null) {
|
||||
try {
|
||||
action.getActionIntent().send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.w(TAG, "Failed to send action", e);
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "RemoteAction is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK
|
||||
&& mListener != null) {
|
||||
mListener.onBackPress();
|
||||
return true;
|
||||
}
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
void onBackPress();
|
||||
void onCloseButtonClick();
|
||||
void onFullscreenButtonClick();
|
||||
}
|
||||
}
|
||||
@@ -208,8 +208,8 @@ class TvPipMenuTests : TvPipTestBase() {
|
||||
@Test
|
||||
fun pipMenu_customActions_override_mediaControls() {
|
||||
// Start media session before entering PiP with custom actions.
|
||||
testApp.clickStartMediaSessionButton()
|
||||
testApp.checkWithCustomActionsCheckbox()
|
||||
testApp.clickStartMediaSessionButton()
|
||||
enterPip_openMenu_assertShown()
|
||||
|
||||
// PiP menu should contain "No-Op", "Off" and "Clear" buttons for the custom actions...
|
||||
|
||||
@@ -26,9 +26,9 @@ import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
|
||||
|
||||
/** Id of the root view in the com.android.wm.shell.pip.tv.PipMenuActivity */
|
||||
private const val TV_PIP_MENU_ROOT_ID = "tv_pip_menu"
|
||||
private const val TV_PIP_MENU_CONTROLS_ID = "pip_controls"
|
||||
private const val TV_PIP_MENU_CLOSE_BUTTON_ID = "close_button"
|
||||
private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "full_button"
|
||||
private const val TV_PIP_MENU_BUTTONS_CONTAINER_ID = "tv_pip_menu_action_buttons"
|
||||
private const val TV_PIP_MENU_CLOSE_BUTTON_ID = "tv_pip_menu_close_button"
|
||||
private const val TV_PIP_MENU_FULLSCREEN_BUTTON_ID = "tv_pip_menu_fullscreen_button"
|
||||
|
||||
private const val FOCUS_ATTEMPTS = 10
|
||||
private const val WAIT_TIME_MS = 3_000L
|
||||
@@ -49,7 +49,7 @@ fun UiDevice.waitForTvPipMenuToClose(): Boolean = wait(Until.gone(tvPipMenuSelec
|
||||
|
||||
fun UiDevice.findTvPipMenuControls(): UiObject2? =
|
||||
findObject(tvPipMenuSelector)
|
||||
?.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_CONTROLS_ID))
|
||||
?.findObject(By.res(SYSTEM_UI_PACKAGE_NAME, TV_PIP_MENU_BUTTONS_CONTAINER_ID))
|
||||
|
||||
fun UiDevice.findTvPipMenuCloseButton(): UiObject2? =
|
||||
findObject(tvPipMenuSelector)?.findObject(TV_PIP_MENU_CLOSE_BUTTON_SELECTOR)
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.android.wm.shell.WindowManagerShellWrapper;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.SystemWindows;
|
||||
import com.android.wm.shell.common.TaskStackListenerImpl;
|
||||
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
import com.android.wm.shell.pip.PipBoundsAlgorithm;
|
||||
import com.android.wm.shell.pip.PipBoundsState;
|
||||
@@ -32,11 +33,8 @@ 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.PipControlsView;
|
||||
import com.android.wm.shell.pip.tv.PipControlsViewController;
|
||||
import com.android.wm.shell.pip.tv.PipNotification;
|
||||
import com.android.wm.shell.pip.tv.TvPipMenuController;
|
||||
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -73,19 +71,6 @@ public abstract class TvPipModule {
|
||||
windowManagerShellWrapper));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static PipControlsViewController providePipControlsViewController(
|
||||
PipControlsView pipControlsView, PipController pipController) {
|
||||
return new PipControlsViewController(pipControlsView, pipController);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static PipControlsView providePipControlsView(Context context) {
|
||||
return new PipControlsView(context, null);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static PipNotification providePipNotification(Context context,
|
||||
@@ -108,9 +93,12 @@ public abstract class TvPipModule {
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static TvPipMenuController providesPipTvMenuController(Context context,
|
||||
PipBoundsState pipBoundsState, SystemWindows systemWindows) {
|
||||
return new TvPipMenuController(context, pipBoundsState, systemWindows);
|
||||
static TvPipMenuController providesPipTvMenuController(
|
||||
Context context,
|
||||
PipBoundsState pipBoundsState,
|
||||
SystemWindows systemWindows,
|
||||
PipMediaController pipMediaController) {
|
||||
return new TvPipMenuController(context, pipBoundsState, systemWindows, pipMediaController);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
|
||||
Reference in New Issue
Block a user