Delegate handle menu operations to a separate class.
Moves the logic for handle menu creation/updating/removal to a separate class out of DesktopModeWindowDecoration. Bug: 276364188 Test: Manual; confirm no regressions with opening/closing/interacting with handle menu. Change-Id: Ie97a32a2a3257bd37d395995e7af75bfc6931add
This commit is contained in:
@@ -17,17 +17,12 @@
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Region;
|
||||
@@ -39,11 +34,6 @@ import android.view.MotionEvent;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.window.SurfaceSyncGroup;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.launcher3.icons.IconProvider;
|
||||
@@ -90,6 +80,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
private AdditionalWindow mHandleMenuAppInfoPill;
|
||||
private AdditionalWindow mHandleMenuWindowingPill;
|
||||
private AdditionalWindow mHandleMenuMoreActionsPill;
|
||||
private HandleMenu mHandleMenu;
|
||||
|
||||
private Drawable mAppIcon;
|
||||
private CharSequence mAppName;
|
||||
@@ -122,29 +113,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
mSyncQueue = syncQueue;
|
||||
|
||||
loadAppInfo();
|
||||
loadHandleMenuDimensions();
|
||||
}
|
||||
|
||||
private void loadHandleMenuDimensions() {
|
||||
final Resources resources = mDecorWindowContext.getResources();
|
||||
mMenuWidth = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_width);
|
||||
mMarginMenuTop = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_margin_top);
|
||||
mMarginMenuStart = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_margin_start);
|
||||
mMarginMenuSpacing = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
|
||||
mAppInfoPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_app_info_pill_height);
|
||||
mWindowingPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_windowing_pill_height);
|
||||
mShadowRadius = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_shadow_radius);
|
||||
mCornerRadius = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_corner_radius);
|
||||
mMoreActionsPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -197,20 +165,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
|
||||
final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
|
||||
|
||||
if (mHandleMenuAppInfoPill != null) {
|
||||
updateHandleMenuPillPositions();
|
||||
startT.setPosition(mHandleMenuAppInfoPill.mWindowSurface,
|
||||
mHandleMenuAppInfoPillPosition.x, mHandleMenuAppInfoPillPosition.y);
|
||||
|
||||
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
|
||||
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
|
||||
if (shouldShowWindowingPill) {
|
||||
startT.setPosition(mHandleMenuWindowingPill.mWindowSurface,
|
||||
mHandleMenuWindowingPillPosition.x, mHandleMenuWindowingPillPosition.y);
|
||||
}
|
||||
|
||||
startT.setPosition(mHandleMenuMoreActionsPill.mWindowSurface,
|
||||
mHandleMenuMoreActionsPillPosition.x, mHandleMenuMoreActionsPillPosition.y);
|
||||
if (isHandleMenuActive()) {
|
||||
mHandleMenu.relayout(startT);
|
||||
}
|
||||
|
||||
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
|
||||
@@ -297,7 +253,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
}
|
||||
|
||||
boolean isHandleMenuActive() {
|
||||
return mHandleMenuAppInfoPill != null;
|
||||
return mHandleMenu != null;
|
||||
}
|
||||
|
||||
private void loadAppInfo() {
|
||||
@@ -327,136 +283,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
* Create and display handle menu window
|
||||
*/
|
||||
void createHandleMenu() {
|
||||
final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
|
||||
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
updateHandleMenuPillPositions();
|
||||
|
||||
createAppInfoPill(t, ssg);
|
||||
|
||||
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
|
||||
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
|
||||
if (shouldShowWindowingPill) {
|
||||
createWindowingPill(t, ssg);
|
||||
}
|
||||
|
||||
createMoreActionsPill(t, ssg);
|
||||
|
||||
ssg.addTransaction(t);
|
||||
ssg.markSyncReady();
|
||||
setupHandleMenu(shouldShowWindowingPill);
|
||||
}
|
||||
|
||||
private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mHandleMenuAppInfoPillPosition.x;
|
||||
final int y = (int) mHandleMenuAppInfoPillPosition.y;
|
||||
mHandleMenuAppInfoPill = addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
|
||||
"Menu's app info pill",
|
||||
t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mHandleMenuWindowingPillPosition.x;
|
||||
final int y = (int) mHandleMenuWindowingPillPosition.y;
|
||||
mHandleMenuWindowingPill = addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
|
||||
"Menu's windowing pill",
|
||||
t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mHandleMenuMoreActionsPillPosition.x;
|
||||
final int y = (int) mHandleMenuMoreActionsPillPosition.y;
|
||||
mHandleMenuMoreActionsPill = addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
|
||||
"Menu's more actions pill",
|
||||
t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
private void setupHandleMenu(boolean windowingPillShown) {
|
||||
// App Info pill setup.
|
||||
final View appInfoPillView = mHandleMenuAppInfoPill.mWindowViewHost.getView();
|
||||
final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
|
||||
final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
|
||||
final TextView appName = appInfoPillView.findViewById(R.id.application_name);
|
||||
collapseBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
appInfoPillView.setOnTouchListener(mOnCaptionTouchListener);
|
||||
appIcon.setImageDrawable(mAppIcon);
|
||||
appName.setText(mAppName);
|
||||
|
||||
// Windowing pill setup.
|
||||
if (windowingPillShown) {
|
||||
final View windowingPillView = mHandleMenuWindowingPill.mWindowViewHost.getView();
|
||||
final ImageButton fullscreenBtn = windowingPillView.findViewById(
|
||||
R.id.fullscreen_button);
|
||||
final ImageButton splitscreenBtn = windowingPillView.findViewById(
|
||||
R.id.split_screen_button);
|
||||
final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
|
||||
final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
|
||||
fullscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
splitscreenBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
floatingBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
desktopBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
// The button corresponding to the windowing mode that the task is currently in uses a
|
||||
// different color than the others.
|
||||
final ColorStateList activeColorStateList = ColorStateList.valueOf(
|
||||
mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
|
||||
final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
|
||||
mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
|
||||
fullscreenBtn.setImageTintList(
|
||||
mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
splitscreenBtn.setImageTintList(
|
||||
mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
}
|
||||
|
||||
// More Actions pill setup.
|
||||
final View moreActionsPillView = mHandleMenuMoreActionsPill.mWindowViewHost.getView();
|
||||
final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
|
||||
closeBtn.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the handle menu pills' position variables to reflect their next positions
|
||||
*/
|
||||
private void updateHandleMenuPillPositions() {
|
||||
final int menuX, menuY;
|
||||
final int captionWidth = mTaskInfo.getConfiguration()
|
||||
.windowConfiguration.getBounds().width();
|
||||
if (mRelayoutParams.mLayoutResId
|
||||
== R.layout.desktop_mode_app_controls_window_decor) {
|
||||
// Align the handle menu to the left of the caption.
|
||||
menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
|
||||
menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
|
||||
} else {
|
||||
// Position the handle menu at the center of the caption.
|
||||
menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
|
||||
menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
|
||||
}
|
||||
|
||||
// App Info pill setup.
|
||||
final int appInfoPillY = menuY;
|
||||
mHandleMenuAppInfoPillPosition.set(menuX, appInfoPillY);
|
||||
|
||||
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
|
||||
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
|
||||
|
||||
final int windowingPillY, moreActionsPillY;
|
||||
if (shouldShowWindowingPill) {
|
||||
windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
|
||||
mHandleMenuWindowingPillPosition.set(menuX, windowingPillY);
|
||||
moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
|
||||
mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
|
||||
} else {
|
||||
// Just start after the end of the app info pill + margins.
|
||||
moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
|
||||
mHandleMenuMoreActionsPillPosition.set(menuX, moreActionsPillY);
|
||||
}
|
||||
mHandleMenu = new HandleMenu.Builder(this)
|
||||
.setAppIcon(mAppIcon)
|
||||
.setAppName(mAppName)
|
||||
.setOnClickListener(mOnCaptionButtonClickListener)
|
||||
.setOnTouchListener(mOnCaptionTouchListener)
|
||||
.setLayoutId(mRelayoutParams.mLayoutResId)
|
||||
.setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
|
||||
.setWindowingButtonsVisible(DesktopModeStatus.isProto2Enabled())
|
||||
.build();
|
||||
mHandleMenu.show();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,14 +300,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
*/
|
||||
void closeHandleMenu() {
|
||||
if (!isHandleMenuActive()) return;
|
||||
mHandleMenuAppInfoPill.releaseView();
|
||||
mHandleMenuAppInfoPill = null;
|
||||
if (mHandleMenuWindowingPill != null) {
|
||||
mHandleMenuWindowingPill.releaseView();
|
||||
mHandleMenuWindowingPill = null;
|
||||
}
|
||||
mHandleMenuMoreActionsPill.releaseView();
|
||||
mHandleMenuMoreActionsPill = null;
|
||||
mHandleMenu.close();
|
||||
mHandleMenu = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -488,10 +318,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
void closeHandleMenuIfNeeded(MotionEvent ev) {
|
||||
if (!isHandleMenuActive()) return;
|
||||
|
||||
// When this is called before the layout is fully inflated, width will be 0.
|
||||
// Menu is not visible in this scenario, so skip the check if that is the case.
|
||||
if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
|
||||
|
||||
PointF inputPoint = offsetCaptionLocation(ev);
|
||||
|
||||
// If this is called before open_menu_button's onClick, we don't want to close
|
||||
@@ -501,22 +327,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
inputPoint.x,
|
||||
inputPoint.y);
|
||||
|
||||
final boolean pointInAppInfoPill = pointInView(
|
||||
mHandleMenuAppInfoPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mHandleMenuAppInfoPillPosition.x,
|
||||
inputPoint.y - mHandleMenuAppInfoPillPosition.y);
|
||||
boolean pointInWindowingPill = false;
|
||||
if (mHandleMenuWindowingPill != null) {
|
||||
pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mHandleMenuWindowingPillPosition.x,
|
||||
inputPoint.y - mHandleMenuWindowingPillPosition.y);
|
||||
}
|
||||
final boolean pointInMoreActionsPill = pointInView(
|
||||
mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
|
||||
inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
|
||||
if (!pointInAppInfoPill && !pointInWindowingPill
|
||||
&& !pointInMoreActionsPill && !pointInOpenMenuButton) {
|
||||
if (!mHandleMenu.isValidMenuInput(inputPoint) && !pointInOpenMenuButton) {
|
||||
closeHandleMenu();
|
||||
}
|
||||
}
|
||||
@@ -573,13 +384,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
final View handle = caption.findViewById(R.id.caption_handle);
|
||||
clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
|
||||
} else {
|
||||
final View appInfoPill = mHandleMenuAppInfoPill.mWindowViewHost.getView();
|
||||
final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
|
||||
// Translate the input point from display coordinates to the same space as the collapse
|
||||
// button, meaning its parent (app info pill view).
|
||||
final PointF inputPoint = new PointF(ev.getX() - mHandleMenuAppInfoPillPosition.x,
|
||||
ev.getY() - mHandleMenuAppInfoPillPosition.y);
|
||||
clickIfPointInView(inputPoint, collapse);
|
||||
mHandleMenu.checkClickEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,7 +396,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean pointInView(View v, float x, float y) {
|
||||
boolean pointInView(View v, float x, float y) {
|
||||
return v != null && v.getLeft() <= x && v.getRight() >= x
|
||||
&& v.getTop() <= y && v.getBottom() >= y;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.windowdecor;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.window.SurfaceSyncGroup;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
||||
|
||||
/**
|
||||
* Handle menu opened when the appropriate button is clicked on.
|
||||
*
|
||||
* Displays up to 3 pills that show the following:
|
||||
* App Info: App name, app icon, and collapse button to close the menu.
|
||||
* Windowing Options(Proto 2 only): Buttons to change windowing modes.
|
||||
* Additional Options: Miscellaneous functions including screenshot and closing task.
|
||||
*/
|
||||
class HandleMenu {
|
||||
private static final String TAG = "HandleMenu";
|
||||
private final Context mContext;
|
||||
private final WindowDecoration mParentDecor;
|
||||
private WindowDecoration.AdditionalWindow mAppInfoPill;
|
||||
private WindowDecoration.AdditionalWindow mWindowingPill;
|
||||
private WindowDecoration.AdditionalWindow mMoreActionsPill;
|
||||
private final PointF mAppInfoPillPosition = new PointF();
|
||||
private final PointF mWindowingPillPosition = new PointF();
|
||||
private final PointF mMoreActionsPillPosition = new PointF();
|
||||
private final boolean mShouldShowWindowingPill;
|
||||
private final Drawable mAppIcon;
|
||||
private final CharSequence mAppName;
|
||||
private final View.OnClickListener mOnClickListener;
|
||||
private final View.OnTouchListener mOnTouchListener;
|
||||
private final RunningTaskInfo mTaskInfo;
|
||||
private final int mLayoutResId;
|
||||
private final int mCaptionX;
|
||||
private final int mCaptionY;
|
||||
private int mMarginMenuTop;
|
||||
private int mMarginMenuStart;
|
||||
private int mMarginMenuSpacing;
|
||||
private int mMenuWidth;
|
||||
private int mAppInfoPillHeight;
|
||||
private int mWindowingPillHeight;
|
||||
private int mMoreActionsPillHeight;
|
||||
private int mShadowRadius;
|
||||
private int mCornerRadius;
|
||||
|
||||
|
||||
HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
|
||||
View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
|
||||
Drawable appIcon, CharSequence appName, boolean shouldShowWindowingPill) {
|
||||
mParentDecor = parentDecor;
|
||||
mContext = mParentDecor.mDecorWindowContext;
|
||||
mTaskInfo = mParentDecor.mTaskInfo;
|
||||
mLayoutResId = layoutResId;
|
||||
mCaptionX = captionX;
|
||||
mCaptionY = captionY;
|
||||
mOnClickListener = onClickListener;
|
||||
mOnTouchListener = onTouchListener;
|
||||
mAppIcon = appIcon;
|
||||
mAppName = appName;
|
||||
mShouldShowWindowingPill = shouldShowWindowingPill;
|
||||
loadHandleMenuDimensions();
|
||||
updateHandleMenuPillPositions();
|
||||
}
|
||||
|
||||
void show() {
|
||||
final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
|
||||
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
|
||||
createAppInfoPill(t, ssg);
|
||||
if (mShouldShowWindowingPill) {
|
||||
createWindowingPill(t, ssg);
|
||||
}
|
||||
createMoreActionsPill(t, ssg);
|
||||
ssg.addTransaction(t);
|
||||
ssg.markSyncReady();
|
||||
setupHandleMenu();
|
||||
}
|
||||
|
||||
private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mAppInfoPillPosition.x;
|
||||
final int y = (int) mAppInfoPillPosition.y;
|
||||
mAppInfoPill = mParentDecor.addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
|
||||
"Menu's app info pill",
|
||||
t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mWindowingPillPosition.x;
|
||||
final int y = (int) mWindowingPillPosition.y;
|
||||
mWindowingPill = mParentDecor.addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
|
||||
"Menu's windowing pill",
|
||||
t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
|
||||
final int x = (int) mMoreActionsPillPosition.x;
|
||||
final int y = (int) mMoreActionsPillPosition.y;
|
||||
mMoreActionsPill = mParentDecor.addWindow(
|
||||
R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
|
||||
"Menu's more actions pill",
|
||||
t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up interactive elements and color of this handle menu
|
||||
*/
|
||||
private void setupHandleMenu() {
|
||||
// App Info pill setup.
|
||||
final View appInfoPillView = mAppInfoPill.mWindowViewHost.getView();
|
||||
final ImageButton collapseBtn = appInfoPillView.findViewById(R.id.collapse_menu_button);
|
||||
final ImageView appIcon = appInfoPillView.findViewById(R.id.application_icon);
|
||||
final TextView appName = appInfoPillView.findViewById(R.id.application_name);
|
||||
collapseBtn.setOnClickListener(mOnClickListener);
|
||||
appInfoPillView.setOnTouchListener(mOnTouchListener);
|
||||
appIcon.setImageDrawable(mAppIcon);
|
||||
appName.setText(mAppName);
|
||||
|
||||
// Windowing pill setup.
|
||||
if (mShouldShowWindowingPill) {
|
||||
final View windowingPillView = mWindowingPill.mWindowViewHost.getView();
|
||||
final ImageButton fullscreenBtn = windowingPillView.findViewById(
|
||||
R.id.fullscreen_button);
|
||||
final ImageButton splitscreenBtn = windowingPillView.findViewById(
|
||||
R.id.split_screen_button);
|
||||
final ImageButton floatingBtn = windowingPillView.findViewById(R.id.floating_button);
|
||||
final ImageButton desktopBtn = windowingPillView.findViewById(R.id.desktop_button);
|
||||
fullscreenBtn.setOnClickListener(mOnClickListener);
|
||||
splitscreenBtn.setOnClickListener(mOnClickListener);
|
||||
floatingBtn.setOnClickListener(mOnClickListener);
|
||||
desktopBtn.setOnClickListener(mOnClickListener);
|
||||
// The button corresponding to the windowing mode that the task is currently in uses a
|
||||
// different color than the others.
|
||||
final ColorStateList activeColorStateList = ColorStateList.valueOf(
|
||||
mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_active));
|
||||
final ColorStateList inActiveColorStateList = ColorStateList.valueOf(
|
||||
mContext.getColor(R.color.desktop_mode_caption_menu_buttons_color_inactive));
|
||||
fullscreenBtn.setImageTintList(
|
||||
mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
splitscreenBtn.setImageTintList(
|
||||
mTaskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
floatingBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
desktopBtn.setImageTintList(mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
|
||||
? activeColorStateList : inActiveColorStateList);
|
||||
}
|
||||
|
||||
// More Actions pill setup.
|
||||
final View moreActionsPillView = mMoreActionsPill.mWindowViewHost.getView();
|
||||
final Button closeBtn = moreActionsPillView.findViewById(R.id.close_button);
|
||||
closeBtn.setOnClickListener(mOnClickListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the handle menu pills' position variables to reflect their next positions
|
||||
*/
|
||||
private void updateHandleMenuPillPositions() {
|
||||
final int menuX, menuY;
|
||||
final int captionWidth = mTaskInfo.getConfiguration()
|
||||
.windowConfiguration.getBounds().width();
|
||||
if (mLayoutResId
|
||||
== R.layout.desktop_mode_app_controls_window_decor) {
|
||||
// Align the handle menu to the left of the caption.
|
||||
menuX = mCaptionX + mMarginMenuStart;
|
||||
menuY = mCaptionY + mMarginMenuTop;
|
||||
} else {
|
||||
// Position the handle menu at the center of the caption.
|
||||
menuX = mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
|
||||
menuY = mCaptionY + mMarginMenuStart;
|
||||
}
|
||||
|
||||
// App Info pill setup.
|
||||
final int appInfoPillY = menuY;
|
||||
mAppInfoPillPosition.set(menuX, appInfoPillY);
|
||||
|
||||
final int windowingPillY, moreActionsPillY;
|
||||
if (mShouldShowWindowingPill) {
|
||||
windowingPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
|
||||
mWindowingPillPosition.set(menuX, windowingPillY);
|
||||
moreActionsPillY = windowingPillY + mWindowingPillHeight + mMarginMenuSpacing;
|
||||
mMoreActionsPillPosition.set(menuX, moreActionsPillY);
|
||||
} else {
|
||||
// Just start after the end of the app info pill + margins.
|
||||
moreActionsPillY = appInfoPillY + mAppInfoPillHeight + mMarginMenuSpacing;
|
||||
mMoreActionsPillPosition.set(menuX, moreActionsPillY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update pill layout, in case task changes have caused positioning to change.
|
||||
* @param t
|
||||
*/
|
||||
void relayout(SurfaceControl.Transaction t) {
|
||||
if (mAppInfoPill != null) {
|
||||
updateHandleMenuPillPositions();
|
||||
t.setPosition(mAppInfoPill.mWindowSurface,
|
||||
mAppInfoPillPosition.x, mAppInfoPillPosition.y);
|
||||
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
|
||||
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
|
||||
if (shouldShowWindowingPill) {
|
||||
t.setPosition(mWindowingPill.mWindowSurface,
|
||||
mWindowingPillPosition.x, mWindowingPillPosition.y);
|
||||
}
|
||||
t.setPosition(mMoreActionsPill.mWindowSurface,
|
||||
mMoreActionsPillPosition.x, mMoreActionsPillPosition.y);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check a passed MotionEvent if a click has occurred on any button on this caption
|
||||
* Note this should only be called when a regular onClick is not possible
|
||||
* (i.e. the button was clicked through status bar layer)
|
||||
* @param ev the MotionEvent to compare against.
|
||||
*/
|
||||
void checkClickEvent(MotionEvent ev) {
|
||||
final View appInfoPill = mAppInfoPill.mWindowViewHost.getView();
|
||||
final ImageButton collapse = appInfoPill.findViewById(R.id.collapse_menu_button);
|
||||
// Translate the input point from display coordinates to the same space as the collapse
|
||||
// button, meaning its parent (app info pill view).
|
||||
final PointF inputPoint = new PointF(ev.getX() - mAppInfoPillPosition.x,
|
||||
ev.getY() - mAppInfoPillPosition.y);
|
||||
if (pointInView(collapse, inputPoint.x, inputPoint.y)) {
|
||||
mOnClickListener.onClick(collapse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A valid menu input is one of the following:
|
||||
* An input that happens in the menu views.
|
||||
* Any input before the views have been laid out.
|
||||
* @param inputPoint the input to compare against.
|
||||
*/
|
||||
boolean isValidMenuInput(PointF inputPoint) {
|
||||
if (!viewsLaidOut()) return true;
|
||||
final boolean pointInAppInfoPill = pointInView(
|
||||
mAppInfoPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mAppInfoPillPosition.x,
|
||||
inputPoint.y - mAppInfoPillPosition.y);
|
||||
boolean pointInWindowingPill = false;
|
||||
if (mWindowingPill != null) {
|
||||
pointInWindowingPill = pointInView(
|
||||
mWindowingPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mWindowingPillPosition.x,
|
||||
inputPoint.y - mWindowingPillPosition.y);
|
||||
}
|
||||
final boolean pointInMoreActionsPill = pointInView(
|
||||
mMoreActionsPill.mWindowViewHost.getView(),
|
||||
inputPoint.x - mMoreActionsPillPosition.x,
|
||||
inputPoint.y - mMoreActionsPillPosition.y);
|
||||
|
||||
return pointInAppInfoPill || pointInWindowingPill || pointInMoreActionsPill;
|
||||
}
|
||||
|
||||
private boolean pointInView(View v, float x, float y) {
|
||||
return v != null && v.getLeft() <= x && v.getRight() >= x
|
||||
&& v.getTop() <= y && v.getBottom() >= y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the views for handle menu can be seen.
|
||||
* @return
|
||||
*/
|
||||
private boolean viewsLaidOut() {
|
||||
return mAppInfoPill.mWindowViewHost.getView().isLaidOut();
|
||||
}
|
||||
|
||||
|
||||
private void loadHandleMenuDimensions() {
|
||||
final Resources resources = mContext.getResources();
|
||||
mMenuWidth = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_width);
|
||||
mMarginMenuTop = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_margin_top);
|
||||
mMarginMenuStart = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_margin_start);
|
||||
mMarginMenuSpacing = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_pill_spacing_margin);
|
||||
mAppInfoPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_app_info_pill_height);
|
||||
mWindowingPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_windowing_pill_height);
|
||||
mMoreActionsPillHeight = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
|
||||
mShadowRadius = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_shadow_radius);
|
||||
mCornerRadius = loadDimensionPixelSize(resources,
|
||||
R.dimen.desktop_mode_handle_menu_corner_radius);
|
||||
}
|
||||
|
||||
private int loadDimensionPixelSize(Resources resources, int resourceId) {
|
||||
if (resourceId == Resources.ID_NULL) {
|
||||
return 0;
|
||||
}
|
||||
return resources.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
|
||||
void close() {
|
||||
mAppInfoPill.releaseView();
|
||||
mAppInfoPill = null;
|
||||
if (mWindowingPill != null) {
|
||||
mWindowingPill.releaseView();
|
||||
mWindowingPill = null;
|
||||
}
|
||||
mMoreActionsPill.releaseView();
|
||||
mMoreActionsPill = null;
|
||||
}
|
||||
|
||||
static final class Builder {
|
||||
private final WindowDecoration mParent;
|
||||
private CharSequence mName;
|
||||
private Drawable mAppIcon;
|
||||
private View.OnClickListener mOnClickListener;
|
||||
private View.OnTouchListener mOnTouchListener;
|
||||
private int mLayoutId;
|
||||
private int mCaptionX;
|
||||
private int mCaptionY;
|
||||
private boolean mShowWindowingPill;
|
||||
|
||||
|
||||
Builder(@NonNull WindowDecoration parent) {
|
||||
mParent = parent;
|
||||
}
|
||||
|
||||
Builder setAppName(@Nullable CharSequence name) {
|
||||
mName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setAppIcon(@Nullable Drawable appIcon) {
|
||||
mAppIcon = appIcon;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setOnClickListener(@Nullable View.OnClickListener onClickListener) {
|
||||
mOnClickListener = onClickListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setOnTouchListener(@Nullable View.OnTouchListener onTouchListener) {
|
||||
mOnTouchListener = onTouchListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setLayoutId(int layoutId) {
|
||||
mLayoutId = layoutId;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setCaptionPosition(int captionX, int captionY) {
|
||||
mCaptionX = captionX;
|
||||
mCaptionY = captionY;
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
|
||||
mShowWindowingPill = windowingButtonsVisible;
|
||||
return this;
|
||||
}
|
||||
|
||||
HandleMenu build() {
|
||||
return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
|
||||
mOnTouchListener, mAppIcon, mName, mShowWindowingPill);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -458,7 +458,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
|
||||
SurfaceControlViewHost mWindowViewHost;
|
||||
Supplier<SurfaceControl.Transaction> mTransactionSupplier;
|
||||
|
||||
private AdditionalWindow(SurfaceControl surfaceControl,
|
||||
AdditionalWindow(SurfaceControl surfaceControl,
|
||||
SurfaceControlViewHost surfaceControlViewHost,
|
||||
Supplier<SurfaceControl.Transaction> transactionSupplier) {
|
||||
mWindowSurface = surfaceControl;
|
||||
|
||||
Reference in New Issue
Block a user