Merge "Enable interacting with caption through status bar." into tm-qpr-dev
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_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:background="@drawable/decor_caption_title">
|
||||
android:background="@drawable/decor_caption_menu_background">
|
||||
<Button
|
||||
style="@style/CaptionButtonStyle"
|
||||
android:id="@+id/fullscreen_button"
|
||||
|
||||
@@ -44,6 +44,8 @@ import android.view.View;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
@@ -71,6 +73,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
|
||||
private DesktopModeController mDesktopModeController;
|
||||
private EventReceiver mEventReceiver;
|
||||
private InputMonitor mInputMonitor;
|
||||
private boolean mTransitionDragActive;
|
||||
|
||||
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
|
||||
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
|
||||
@@ -91,6 +94,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mDisplayController = displayController;
|
||||
mSyncQueue = syncQueue;
|
||||
mDesktopModeController = desktopModeController;
|
||||
mTransitionDragActive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,7 +292,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
|
||||
mDragResizeCallback.onDragResizeEnd(
|
||||
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
|
||||
if (e.getRawY(dragPointerIdx) <= statusBarHeight
|
||||
&& windowingMode == WINDOWING_MODE_FREEFORM) {
|
||||
&& DesktopModeStatus.isActive(mContext)) {
|
||||
mDesktopModeController.setDesktopModeActive(false);
|
||||
}
|
||||
break;
|
||||
@@ -306,26 +310,97 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
|
||||
@Override
|
||||
public void onInputEvent(InputEvent event) {
|
||||
boolean handled = false;
|
||||
if (event instanceof MotionEvent
|
||||
&& ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
|
||||
if (event instanceof MotionEvent) {
|
||||
handled = true;
|
||||
CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
|
||||
CaptionWindowDecorViewModel.this.handleReceivedMotionEvent((MotionEvent) event);
|
||||
}
|
||||
finishInputEvent(event, handled);
|
||||
}
|
||||
}
|
||||
|
||||
// If any input received is outside of caption bounds, turn off handle menu
|
||||
private void handleMotionEvent(MotionEvent ev) {
|
||||
int size = mWindowDecorByTaskId.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
|
||||
if (decoration != null) {
|
||||
decoration.closeHandleMenuIfNeeded(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();
|
||||
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) {
|
||||
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
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
|
||||
*
|
||||
*/
|
||||
public void setButtonVisibility() {
|
||||
void setButtonVisibility() {
|
||||
mDesktopActive = DesktopModeStatus.isActive(mContext);
|
||||
int v = mDesktopActive ? View.VISIBLE : View.GONE;
|
||||
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);
|
||||
}
|
||||
|
||||
public boolean isHandleMenuActive() {
|
||||
boolean isHandleMenuActive() {
|
||||
return mHandleMenu != null;
|
||||
}
|
||||
|
||||
@@ -277,7 +278,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
|
||||
/**
|
||||
* Create and display handle menu window
|
||||
*/
|
||||
public void createHandleMenu() {
|
||||
void createHandleMenu() {
|
||||
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
final Resources resources = mDecorWindowContext.getResources();
|
||||
int x = mRelayoutParams.mCaptionX;
|
||||
@@ -298,7 +299,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
|
||||
/**
|
||||
* Close the handle menu window
|
||||
*/
|
||||
public void closeHandleMenu() {
|
||||
void closeHandleMenu() {
|
||||
if (!isHandleMenuActive()) return;
|
||||
mHandleMenu.releaseView();
|
||||
mHandleMenu = null;
|
||||
@@ -313,24 +314,85 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
|
||||
/**
|
||||
* Close an open handle menu if input is outside of menu coordinates
|
||||
* @param ev the tapped point to compare against
|
||||
* @return
|
||||
*/
|
||||
public void closeHandleMenuIfNeeded(MotionEvent ev) {
|
||||
if (mHandleMenu != null) {
|
||||
Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
|
||||
.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)) {
|
||||
void closeHandleMenuIfNeeded(MotionEvent ev) {
|
||||
if (isHandleMenuActive()) {
|
||||
if (!checkEventInCaptionView(ev, R.id.caption)) {
|
||||
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
|
||||
public void close() {
|
||||
closeDragResizeListener();
|
||||
|
||||
Reference in New Issue
Block a user