Merge "Enable interacting with caption through status bar." into tm-qpr-dev

This commit is contained in:
Matt Sziklay
2022-11-18 18:22:42 +00:00
committed by Android (Google) Code Review
4 changed files with 186 additions and 28 deletions

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2022 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/white" />
<corners android:radius="20dp" />
</shape>

View File

@@ -20,7 +20,7 @@ android:id="@+id/handle_menu"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title"> android:background="@drawable/decor_caption_menu_background">
<Button <Button
style="@style/CaptionButtonStyle" style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button" android:id="@+id/fullscreen_button"

View File

@@ -44,6 +44,8 @@ import android.view.View;
import android.window.WindowContainerToken; import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayController;
@@ -71,6 +73,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeController mDesktopModeController; private DesktopModeController mDesktopModeController;
private EventReceiver mEventReceiver; private EventReceiver mEventReceiver;
private InputMonitor mInputMonitor; private InputMonitor mInputMonitor;
private boolean mTransitionDragActive;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl(); private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
@@ -91,6 +94,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDisplayController = displayController; mDisplayController = displayController;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mDesktopModeController = desktopModeController; mDesktopModeController = desktopModeController;
mTransitionDragActive = false;
} }
@Override @Override
@@ -288,7 +292,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mDragResizeCallback.onDragResizeEnd( mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
if (e.getRawY(dragPointerIdx) <= statusBarHeight if (e.getRawY(dragPointerIdx) <= statusBarHeight
&& windowingMode == WINDOWING_MODE_FREEFORM) { && DesktopModeStatus.isActive(mContext)) {
mDesktopModeController.setDesktopModeActive(false); mDesktopModeController.setDesktopModeActive(false);
} }
break; break;
@@ -306,26 +310,97 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
@Override @Override
public void onInputEvent(InputEvent event) { public void onInputEvent(InputEvent event) {
boolean handled = false; boolean handled = false;
if (event instanceof MotionEvent if (event instanceof MotionEvent) {
&& ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
handled = true; handled = true;
CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event); CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
} }
finishInputEvent(event, handled); finishInputEvent(event, handled);
} }
} }
// If any input received is outside of caption bounds, turn off handle menu /**
private void handleMotionEvent(MotionEvent ev) { * Handle MotionEvents relevant to focused task's caption that don't directly touch it
int size = mWindowDecorByTaskId.size(); * @param ev the {@link MotionEvent} received by {@link EventReceiver}
for (int i = 0; i < size; i++) { */
CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i); private void handleReceivedMotionEvent(MotionEvent ev) {
if (decoration != null) { if (!DesktopModeStatus.isActive(mContext)) {
decoration.closeHandleMenuIfNeeded(ev); handleCaptionThroughStatusBar(ev);
}
handleEventOutsideFocusedCaption(ev);
// Prevent status bar from reacting to a caption drag.
if (mTransitionDragActive && !DesktopModeStatus.isActive(mContext)) {
mInputMonitor.pilferPointers();
}
}
// If an UP/CANCEL action is received outside of caption bounds, turn off handle menu
private void handleEventOutsideFocusedCaption(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
CaptionWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
return;
}
if (!mTransitionDragActive) {
focusedDecor.closeHandleMenuIfNeeded(ev);
} }
} }
} }
/**
* Perform caption actions if not able to through normal means.
* Turn on desktop mode if handle is dragged below status bar.
*/
private void handleCaptionThroughStatusBar(MotionEvent ev) {
switch (ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
// Begin drag through status bar if applicable.
CaptionWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor != null && !DesktopModeStatus.isActive(mContext)
&& focusedDecor.checkTouchEventInHandle(ev)) {
mTransitionDragActive = true;
}
break;
}
case MotionEvent.ACTION_UP: {
CaptionWindowDecoration focusedDecor = getFocusedDecor();
if (focusedDecor == null) {
mTransitionDragActive = false;
return;
}
if (mTransitionDragActive) {
mTransitionDragActive = false;
int statusBarHeight = mDisplayController
.getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
if (ev.getY() > statusBarHeight) {
mDesktopModeController.setDesktopModeActive(true);
return;
}
}
focusedDecor.checkClickEvent(ev);
break;
}
case MotionEvent.ACTION_CANCEL: {
mTransitionDragActive = false;
}
}
}
@Nullable
private CaptionWindowDecoration getFocusedDecor() {
int size = mWindowDecorByTaskId.size();
CaptionWindowDecoration focusedDecor = null;
for (int i = 0; i < size; i++) {
CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
if (decor != null && decor.isFocused()) {
focusedDecor = decor;
break;
}
}
return focusedDecor;
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true; if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;

View File

@@ -23,6 +23,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
import android.os.Handler; import android.os.Handler;
@@ -243,7 +244,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
* Sets the visibility of buttons and color of caption based on desktop mode status * Sets the visibility of buttons and color of caption based on desktop mode status
* *
*/ */
public void setButtonVisibility() { void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext); mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE; int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption); View caption = mResult.mRootView.findViewById(R.id.caption);
@@ -262,7 +263,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT); caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
} }
public boolean isHandleMenuActive() { boolean isHandleMenuActive() {
return mHandleMenu != null; return mHandleMenu != null;
} }
@@ -277,7 +278,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
/** /**
* Create and display handle menu window * Create and display handle menu window
*/ */
public void createHandleMenu() { void createHandleMenu() {
SurfaceControl.Transaction t = new SurfaceControl.Transaction(); SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources(); final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX; int x = mRelayoutParams.mCaptionX;
@@ -298,7 +299,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
/** /**
* Close the handle menu window * Close the handle menu window
*/ */
public void closeHandleMenu() { void closeHandleMenu() {
if (!isHandleMenuActive()) return; if (!isHandleMenuActive()) return;
mHandleMenu.releaseView(); mHandleMenu.releaseView();
mHandleMenu = null; mHandleMenu = null;
@@ -313,24 +314,85 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
/** /**
* Close an open handle menu if input is outside of menu coordinates * Close an open handle menu if input is outside of menu coordinates
* @param ev the tapped point to compare against * @param ev the tapped point to compare against
* @return
*/ */
public void closeHandleMenuIfNeeded(MotionEvent ev) { void closeHandleMenuIfNeeded(MotionEvent ev) {
if (mHandleMenu != null) { if (isHandleMenuActive()) {
Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId) if (!checkEventInCaptionView(ev, R.id.caption)) {
.positionInParent;
final Resources resources = mDecorWindowContext.getResources();
ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
ev.offsetLocation(-positionInParent.x, -positionInParent.y);
int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
if (!(ev.getX() >= 0 && ev.getY() >= 0
&& ev.getX() <= width && ev.getY() <= height)) {
closeHandleMenu(); closeHandleMenu();
} }
} }
} }
boolean isFocused() {
return mTaskInfo.isFocused;
}
/**
* Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
* @param ev the {@link MotionEvent} to offset
* @return the point of the input in local space
*/
private PointF offsetCaptionLocation(MotionEvent ev) {
PointF result = new PointF(ev.getX(), ev.getY());
Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
.positionInParent;
result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
result.offset(-positionInParent.x, -positionInParent.y);
return result;
}
/**
* Determine if a passed MotionEvent is in a view in caption
* @param ev the {@link MotionEvent} to check
* @param layoutId the id of the view
* @return {@code true} if event is inside the specified view, {@code false} if not
*/
private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
if (mResult.mRootView == null) return false;
PointF inputPoint = offsetCaptionLocation(ev);
View view = mResult.mRootView.findViewById(layoutId);
return view != null && view.pointInView(inputPoint.x, inputPoint.y, 0);
}
boolean checkTouchEventInHandle(MotionEvent ev) {
if (isHandleMenuActive()) return false;
return checkEventInCaptionView(ev, R.id.caption_handle);
}
/**
* 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
*/
void checkClickEvent(MotionEvent ev) {
if (mResult.mRootView == null) return;
View caption = mResult.mRootView.findViewById(R.id.caption);
PointF inputPoint = offsetCaptionLocation(ev);
if (!isHandleMenuActive()) {
View handle = caption.findViewById(R.id.caption_handle);
clickIfPointInView(inputPoint, handle);
} else {
View menu = mHandleMenu.mWindowViewHost.getView();
View fullscreen = menu.findViewById(R.id.fullscreen_button);
if (clickIfPointInView(inputPoint, fullscreen)) return;
View desktop = menu.findViewById(R.id.desktop_button);
if (clickIfPointInView(inputPoint, desktop)) return;
View split = menu.findViewById(R.id.split_screen_button);
if (clickIfPointInView(inputPoint, split)) return;
View more = menu.findViewById(R.id.more_button);
clickIfPointInView(inputPoint, more);
}
}
private boolean clickIfPointInView(PointF inputPoint, View v) {
if (v.pointInView(inputPoint.x - v.getLeft(), inputPoint.y, 0)) {
mOnCaptionButtonClickListener.onClick(v);
return true;
}
return false;
}
@Override @Override
public void close() { public void close() {
closeDragResizeListener(); closeDragResizeListener();