Merge "Enable interacting with caption through status bar." into tm-qpr-dev am: d1aa46cea9
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20407068 Change-Id: Ia87119ac800b7ed008706de9ba5620760e7029af Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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>
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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,24 +310,95 @@ 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
|
||||||
|
* @param ev the {@link MotionEvent} received by {@link EventReceiver}
|
||||||
|
*/
|
||||||
|
private void handleReceivedMotionEvent(MotionEvent ev) {
|
||||||
|
if (!DesktopModeStatus.isActive(mContext)) {
|
||||||
|
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();
|
int size = mWindowDecorByTaskId.size();
|
||||||
|
CaptionWindowDecoration focusedDecor = null;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
|
CaptionWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
|
||||||
if (decoration != null) {
|
if (decor != null && decor.isFocused()) {
|
||||||
decoration.closeHandleMenuIfNeeded(ev);
|
focusedDecor = decor;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return focusedDecor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user