Add handle menu to bar type window caption.

Adds a menu on clicking handle that will enable windowing changes. Still
to implement for this prototype: More button, Split Screen button,
Overtop/Float button.

Bug: 239960071
Test: Launch various tasks, confirm handle menu appears, drag
functionality is unaffected.

Change-Id: I0aeeffb44e6bfd580c70270e562d8c04031892de
This commit is contained in:
mattsziklay
2022-10-06 13:24:33 -07:00
parent ef6a85b104
commit dde7e758e8
15 changed files with 623 additions and 39 deletions

View File

@@ -0,0 +1,31 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0"
>
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="6.0"
android:translateY="6.0">
<path
android:fillColor="@android:color/black"
android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
</group>
</vector>

View File

@@ -0,0 +1,31 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0"
>
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="6.0"
android:translateY="6.0">
<path
android:fillColor="@android:color/black"
android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
</group>
</vector>

View File

@@ -0,0 +1,31 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0"
>
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="6.0"
android:translateY="6.0">
<path
android:fillColor="@android:color/black"
android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
</group>
</vector>

View File

@@ -0,0 +1,31 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0"
>
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="6.0"
android:translateY="6.0">
<path
android:fillColor="@android:color/black"
android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
</group>
</vector>

View File

@@ -0,0 +1,28 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="32.0"
android:viewportHeight="32.0"
>
<group android:translateX="6.0"
android:translateY="8.0">
<path
android:fillColor="@android:color/black"
android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/> </group>
</vector>

View File

@@ -0,0 +1,30 @@
<?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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="210.0dp"
android:height="64.0dp"
android:tint="@color/decor_button_light_color"
>
<group android:scaleX="0.5"
android:scaleY="0.5"
android:translateX="8.0"
android:translateY="8.0" >
<path
android:fillColor="@android:color/white"
android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
</group>
</vector>

View File

@@ -0,0 +1,49 @@
<?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.
-->
<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/fullscreen_button"
android:contentDescription="@string/fullscreen_text"
android:background="@drawable/caption_fullscreen_button"/>
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/split_screen_button"
android:contentDescription="@string/split_screen_text"
android:background="@drawable/caption_split_screen_button"/>
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/floating_button"
android:contentDescription="@string/float_button_text"
android:background="@drawable/caption_floating_button"/>
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/desktop_button"
android:contentDescription="@string/desktop_text"
android:background="@drawable/caption_desktop_button"/>
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/more_button"
android:contentDescription="@string/more_button_text"
android:background="@drawable/caption_more_button"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>

View File

@@ -19,14 +19,10 @@
android:id="@+id/caption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:background="@drawable/decor_caption_title">
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/back_button"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:contentDescription="@string/back_button_text"
android:background="@drawable/decor_back_button_dark"
/>
@@ -39,11 +35,8 @@
android:contentDescription="@string/handle_text"
android:background="@drawable/decor_handle_dark"/>
<Button
style="@style/CaptionButtonStyle"
android:id="@+id/close_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark"/>
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>

View File

@@ -197,4 +197,14 @@
<string name="back_button_text">Back</string>
<!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
<string name="handle_text">Handle</string>
<!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
<string name="fullscreen_text">Fullscreen</string>
<!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
<string name="desktop_text">Desktop Mode</string>
<!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
<string name="split_screen_text">Split Screen</string>
<!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
<string name="more_button_text">More</string>
<!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
<string name="float_button_text">Float</string>
</resources>

View File

@@ -30,6 +30,13 @@
<item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
</style>
<style name="CaptionButtonStyle">
<item name="android:layout_width">32dp</item>
<item name="android:layout_height">32dp</item>
<item name="android:layout_margin">5dp</item>
<item name="android:padding">4dp</item>
</style>
<style name="DockedDividerBackground">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">@dimen/split_divider_bar_width</item>

View File

@@ -26,11 +26,16 @@ import android.app.ActivityTaskManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.view.Choreographer;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -64,8 +69,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
private DesktopModeController mDesktopModeController;
private EventReceiver mEventReceiver;
private InputMonitor mInputMonitor;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
public CaptionWindowDecorViewModel(
Context context,
@@ -108,12 +116,19 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
mDragStartListener);
CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
setupWindowDecorationForTransition(taskInfo, startT, finishT);
if (mInputMonitor == null) {
mInputMonitor = InputManager.getInstance().monitorGestureInput(
"caption-touch", mContext.getDisplayId());
mEventReceiver = new EventReceiver(
mInputMonitor.getInputChannel(), Looper.myLooper());
}
return true;
}
@@ -165,6 +180,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onClick(View v) {
CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
if (id == R.id.close_window) {
WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
} else if (id == R.id.back_button) {
injectBackKey();
} else if (id == R.id.caption_handle) {
decoration.createHandleMenu();
} else if (id == R.id.desktop_button) {
mDesktopModeController.setDesktopModeActive(true);
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
mDesktopModeController.setDesktopModeActive(false);
decoration.closeHandleMenu();
decoration.setButtonVisibility();
}
}
private void injectBackKey() {
@@ -257,6 +282,36 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
}
// InputEventReceiver to listen for touch input outside of caption bounds
private class EventReceiver extends InputEventReceiver {
EventReceiver(InputChannel channel, Looper looper) {
super(channel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
boolean handled = false;
if (event instanceof MotionEvent
&& ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
handled = true;
CaptionWindowDecorViewModel.this.handleMotionEvent((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);
}
}
}
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
@Override
public void onDragStart(int taskId) {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
}
}
}

View File

@@ -20,10 +20,14 @@ import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.VectorDrawable;
import android.os.Handler;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private boolean mDesktopActive;
private AdditionalWindow mHandleMenu;
CaptionWindowDecoration(
Context context,
DisplayController displayController,
@@ -123,7 +129,20 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
if (isDragResizeable) {
mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
}
final Resources resources = mDecorWindowContext.getResources();
final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
final int captionHeight = loadDimensionPixelSize(resources,
mRelayoutParams.mCaptionHeightId);
final int captionWidth = loadDimensionPixelSize(resources,
mRelayoutParams.mCaptionWidthId);
final int captionLeft = taskBounds.width() / 2
- captionWidth / 2;
final int captionTop = taskBounds.top
<= captionHeight / 2 ? 0 : -captionHeight / 2;
mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
taskInfo = null; // Clear it just in case we use it accidentally
mTaskOrganizer.applyTransaction(wct);
@@ -137,15 +156,14 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
}
// If this task is not focused, do not show caption.
setCaptionVisibility(taskInfo.isFocused);
setCaptionVisibility(mTaskInfo.isFocused);
// Only handle should show if Desktop Mode is inactive.
boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
mDesktopActive = desktopCurrentStatus;
setButtonVisibility();
}
taskInfo = null; // Clear it just in case we use it accidentally
if (!isDragResizeable) {
closeDragResizeListener();
@@ -184,9 +202,22 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
back.setOnClickListener(mOnCaptionButtonClickListener);
View handle = caption.findViewById(R.id.caption_handle);
handle.setOnTouchListener(mOnCaptionTouchListener);
handle.setOnClickListener(mOnCaptionButtonClickListener);
setButtonVisibility();
}
private void setupHandleMenu() {
View menu = mHandleMenu.mWindowViewHost.getView();
View fullscreen = menu.findViewById(R.id.fullscreen_button);
fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
View desktop = menu.findViewById(R.id.desktop_button);
desktop.setOnClickListener(mOnCaptionButtonClickListener);
View split = menu.findViewById(R.id.split_screen_button);
split.setOnClickListener(mOnCaptionButtonClickListener);
View more = menu.findViewById(R.id.more_button);
more.setOnClickListener(mOnCaptionButtonClickListener);
}
/**
* Sets caption visibility based on task focus.
*
@@ -194,8 +225,9 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
*/
private void setCaptionVisibility(boolean visible) {
int v = visible ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setVisibility(v);
View captionView = mResult.mRootView.findViewById(R.id.caption);
captionView.setVisibility(v);
if (!visible) closeHandleMenu();
}
/**
@@ -203,6 +235,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
*
*/
public void setButtonVisibility() {
mDesktopActive = DesktopModeStatus.isActive(mContext);
int v = mDesktopActive ? View.VISIBLE : View.GONE;
View caption = mResult.mRootView.findViewById(R.id.caption);
View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
}
public boolean isHandleMenuActive() {
return mHandleMenu != null;
}
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
@@ -228,9 +265,67 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragResizeListener = null;
}
/**
* Create and display handle menu window
*/
public void createHandleMenu() {
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
int y = mRelayoutParams.mCaptionY;
int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String namePrefix = "Caption Menu";
mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
width, height);
mSyncQueue.runInSync(transaction -> {
transaction.merge(t);
t.close();
});
setupHandleMenu();
}
/**
* Close the handle menu window
*/
public void closeHandleMenu() {
if (!isHandleMenuActive()) return;
mHandleMenu.releaseView();
mHandleMenu = null;
}
@Override
void releaseViews() {
closeHandleMenu();
super.releaseViews();
}
/**
* 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)) {
closeHandleMenu();
}
}
}
@Override
public void close() {
closeDragResizeListener();
closeHandleMenu();
super.close();
}
}

View File

@@ -42,14 +42,18 @@ class TaskPositioner implements DragResizeCallback {
private final Rect mResizeTaskBounds = new Rect();
private int mCtrlType;
private DragStartListener mDragStartListener;
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
DragStartListener dragStartListener) {
mTaskOrganizer = taskOrganizer;
mWindowDecoration = windowDecoration;
mDragStartListener = dragStartListener;
}
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@ class TaskPositioner implements DragResizeCallback {
mTaskOrganizer.applyTransaction(wct);
}
}
interface DragStartListener {
/**
* Inform the implementing class that a drag resize has started
* @param taskId id of this positioner's {@link WindowDecoration}
*/
void onDragStart(int taskId);
}
}

View File

@@ -200,16 +200,17 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
outResult.mWidth = taskBounds.width()
+ loadDimensionPixelSize(resources, params.mOutsetRightId)
- decorContainerOffsetX;
- outResult.mDecorContainerOffsetX;
outResult.mHeight = taskBounds.height()
+ loadDimensionPixelSize(resources, params.mOutsetBottomId)
- decorContainerOffsetY;
- outResult.mDecorContainerOffsetY;
startT.setPosition(
mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
mDecorationContainerSurface,
outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
.setWindowCrop(mDecorationContainerSurface,
outResult.mWidth, outResult.mHeight)
// TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
//Prevent caption from going offscreen if task is too high up
final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
startT.setPosition(
mCaptionContainerSurface, -decorContainerOffsetX
+ taskBounds.width() / 2 - captionWidth / 2,
-decorContainerOffsetY - captionYPos)
.setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
mCaptionContainerSurface,
-outResult.mDecorContainerOffsetX + params.mCaptionX,
-outResult.mDecorContainerOffsetY + params.mCaptionY)
.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Caption insets
mCaptionInsetsRect.set(taskBounds);
mCaptionInsetsRect.bottom =
mCaptionInsetsRect.top + captionHeight - captionYPos;
mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
CAPTION_INSETS_TYPES);
} else {
@@ -302,10 +300,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
mTaskSurfaceCrop.set(
decorContainerOffsetX,
decorContainerOffsetY,
outResult.mWidth + decorContainerOffsetX,
outResult.mHeight + decorContainerOffsetY);
outResult.mDecorContainerOffsetX,
outResult.mDecorContainerOffsetY,
outResult.mWidth + outResult.mDecorContainerOffsetX,
outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
.setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return true;
}
private void releaseViews() {
void releaseViews() {
if (mViewHost != null) {
mViewHost.release();
mViewHost = null;
@@ -369,20 +367,60 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
releaseViews();
}
private static int loadDimensionPixelSize(Resources resources, int resourceId) {
static int loadDimensionPixelSize(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimensionPixelSize(resourceId);
}
private static float loadDimension(Resources resources, int resourceId) {
static float loadDimension(Resources resources, int resourceId) {
if (resourceId == Resources.ID_NULL) {
return 0;
}
return resources.getDimension(resourceId);
}
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
* @param layoutId layout to make the window from
* @param t the transaction to apply
* @param xPos x position of new window
* @param yPos y position of new window
* @param width width of new window
* @param height height of new window
* @return
*/
AdditionalWindow addWindow(int layoutId, String namePrefix,
SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
.setContainerLayer()
.setParent(mDecorationContainerSurface)
.build();
View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
t.setPosition(
windowSurfaceControl, xPos, yPos)
.setWindowCrop(windowSurfaceControl, width, height)
.show(windowSurfaceControl);
final WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
lp.setTrustedOverlay();
WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
windowSurfaceControl, null /* hostInputToken */);
SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
.create(mDecorWindowContext, mDisplay, windowManager);
viewHost.setView(v, lp);
return new AdditionalWindow(windowSurfaceControl, viewHost,
mSurfaceControlTransactionSupplier);
}
static class RelayoutParams{
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
@@ -395,6 +433,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mOutsetLeftId;
int mOutsetRightId;
int mCaptionX;
int mCaptionY;
void setOutsets(int leftId, int topId, int rightId, int bottomId) {
mOutsetLeftId = leftId;
mOutsetTopId = topId;
@@ -402,6 +443,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mOutsetBottomId = bottomId;
}
void setCaptionPosition(int left, int top) {
mCaptionX = left;
mCaptionY = top;
}
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mOutsetBottomId = Resources.ID_NULL;
mOutsetLeftId = Resources.ID_NULL;
mOutsetRightId = Resources.ID_NULL;
mCaptionX = 0;
mCaptionY = 0;
}
}
@@ -419,10 +468,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mWidth;
int mHeight;
T mRootView;
int mDecorContainerOffsetX;
int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
mDecorContainerOffsetX = 0;
mDecorContainerOffsetY = 0;
mRootView = null;
}
}
@@ -432,4 +485,41 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
return new SurfaceControlViewHost(c, d, wmm);
}
}
/**
* Subclass for additional windows associated with this WindowDecoration
*/
static class AdditionalWindow {
SurfaceControl mWindowSurface;
SurfaceControlViewHost mWindowViewHost;
Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private AdditionalWindow(SurfaceControl surfaceControl,
SurfaceControlViewHost surfaceControlViewHost,
Supplier<SurfaceControl.Transaction> transactionSupplier) {
mWindowSurface = surfaceControl;
mWindowViewHost = surfaceControlViewHost;
mTransactionSupplier = transactionSupplier;
}
void releaseView() {
WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
if (mWindowViewHost != null) {
mWindowViewHost.release();
mWindowViewHost = null;
}
windowManager = null;
final SurfaceControl.Transaction t = mTransactionSupplier.get();
boolean released = false;
if (mWindowSurface != null) {
t.remove(mWindowSurface);
mWindowSurface = null;
released = true;
}
if (released) {
t.apply();
}
}
}
}

View File

@@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -64,6 +65,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
@@ -102,12 +104,14 @@ public class WindowDecorationTests extends ShellTestCase {
private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
private SurfaceControl.Transaction mMockSurfaceControlStartT;
private SurfaceControl.Transaction mMockSurfaceControlFinishT;
private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
@Before
public void setUp() {
mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
.create(any(), any(), any());
@@ -227,8 +231,8 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockView).setTaskFocusState(true);
verify(mMockWindowContainerTransaction)
.addRectInsetsProvider(taskInfo.token,
new Rect(100, 300, 400, 332),
new Rect(100, 300, 400, 364),
new int[] { InsetsState.ITYPE_CAPTION_BAR });
}
@@ -366,6 +370,71 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
@Test
public void testAddWindow() {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController)
.getDisplay(Display.DEFAULT_DISPLAY);
final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder decorContainerSurfaceBuilder =
createMockSurfaceControlBuilder(decorContainerSurface);
mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
createMockSurfaceControlBuilder(taskBackgroundSurface);
mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder captionContainerSurfaceBuilder =
createMockSurfaceControlBuilder(captionContainerSurface);
mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
mMockSurfaceControlTransactions.add(t);
final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
new ActivityManager.TaskDescription.Builder()
.setBackgroundColor(Color.YELLOW);
final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
.setDisplayId(Display.DEFAULT_DISPLAY)
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setBounds(TASK_BOUNDS)
.setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
.setVisible(true)
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
mRelayoutParams.setOutsets(
R.dimen.test_window_decor_left_outset,
R.dimen.test_window_decor_top_outset,
R.dimen.test_window_decor_right_outset,
R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
windowDecor.relayout(taskInfo);
final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
final SurfaceControl.Builder additionalWindowSurfaceBuilder =
createMockSurfaceControlBuilder(additionalWindowSurface);
mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
.create(any(), eq(defaultDisplay), any());
assertThat(additionalWindow.mWindowViewHost).isNotNull();
additionalWindow.releaseView();
assertThat(additionalWindow.mWindowViewHost).isNull();
assertThat(additionalWindow.mWindowSurface).isNull();
}
private TestWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@ public class WindowDecorationTests extends ShellTestCase {
relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
mMockWindowContainerTransaction, mMockView, mRelayoutResult);
}
private WindowDecoration.AdditionalWindow addTestWindow() {
final Resources resources = mDecorWindowContext.getResources();
int x = mRelayoutParams.mCaptionX;
int y = mRelayoutParams.mCaptionY;
int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
x - mRelayoutResult.mDecorContainerOffsetX,
y - mRelayoutResult.mDecorContainerOffsetY,
width, height);
return additionalWindow;
}
}
}