Merge changes from topic "caption_in_wm_shell" into tm-qpr-dev
* changes: Support moving and resizing freeform windows Render window decoration in WM shell
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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item app:state_task_focused="true" android:color="#FF000000" />
|
||||
<item android:color="#33000000" />
|
||||
</selector>
|
||||
@@ -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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item app:state_task_focused="true" android:color="#FFFFFFFF" />
|
||||
<item android:color="#33FFFFFF" />
|
||||
</selector>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?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.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<!-- Fading the to 85% blackness -->
|
||||
<item app:state_task_focused="true" android:color="#D8D8D8" />
|
||||
<!-- Fading the to 95% blackness -->
|
||||
<item android:color="#F2F2F2" />
|
||||
</selector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?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"
|
||||
android:tintMode="multiply"
|
||||
android:tint="@color/decor_caption_title_color"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?android:attr/colorPrimary" />
|
||||
</shape>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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"
|
||||
android:tint="@color/decor_button_dark_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="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?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"
|
||||
android:tint="@color/decor_button_dark_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="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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/caption"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:background="@drawable/decor_caption_title">
|
||||
<Button
|
||||
android:id="@+id/maximize_window"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_margin="5dp"
|
||||
android:padding="4dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:contentDescription="@string/maximize_button_text"
|
||||
android:background="@drawable/decor_maximize_button_dark"
|
||||
android:duplicateParentState="true"/>
|
||||
<Button
|
||||
android:id="@+id/close_window"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_margin="5dp"
|
||||
android:padding="4dp"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:contentDescription="@string/close_button_text"
|
||||
android:background="@drawable/decor_close_button_dark"
|
||||
android:duplicateParentState="true"/>
|
||||
</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
|
||||
|
||||
@@ -19,4 +19,8 @@
|
||||
<attr name="icon" format="reference" />
|
||||
<attr name="text" format="string" />
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="MessageState">
|
||||
<attr name="state_task_focused" format="boolean"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
@@ -186,4 +186,12 @@
|
||||
<!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
|
||||
<string name="letterbox_education_got_it">Got it</string>
|
||||
|
||||
<!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
|
||||
<string name="letterbox_education_expand_button_description">Expand for more information.</string>
|
||||
|
||||
<!-- Freeform window caption strings -->
|
||||
<!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
|
||||
<string name="maximize_button_text">Maximize</string>
|
||||
<!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
|
||||
<string name="close_button_text">Close</string>
|
||||
</resources>
|
||||
|
||||
@@ -57,7 +57,7 @@ public class ShellInitImpl {
|
||||
private final FullscreenTaskListener mFullscreenTaskListener;
|
||||
private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
|
||||
private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
|
||||
private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
|
||||
private final Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
private final Transitions mTransitions;
|
||||
private final StartingWindowController mStartingWindow;
|
||||
@@ -78,7 +78,7 @@ public class ShellInitImpl {
|
||||
FullscreenTaskListener fullscreenTaskListener,
|
||||
Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
|
||||
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
|
||||
Optional<FreeformTaskListener> freeformTaskListenerOptional,
|
||||
Optional<FreeformTaskListener<?>> freeformTaskListenerOptional,
|
||||
Optional<RecentTasksController> recentTasks,
|
||||
Transitions transitions,
|
||||
StartingWindowController startingWindow,
|
||||
|
||||
@@ -347,12 +347,12 @@ public abstract class WMShellBaseModule {
|
||||
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
|
||||
@BindsOptionalOf
|
||||
@DynamicOverride
|
||||
abstract FreeformTaskListener optionalFreeformTaskListener();
|
||||
abstract FreeformTaskListener<?> optionalFreeformTaskListener();
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Optional<FreeformTaskListener> provideFreeformTaskListener(
|
||||
@DynamicOverride Optional<FreeformTaskListener> freeformTaskListener,
|
||||
static Optional<FreeformTaskListener<?>> provideFreeformTaskListener(
|
||||
@DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener,
|
||||
Context context) {
|
||||
if (FreeformTaskListener.isFreeformEnabled(context)) {
|
||||
return freeformTaskListener;
|
||||
@@ -642,7 +642,7 @@ public abstract class WMShellBaseModule {
|
||||
FullscreenTaskListener fullscreenTaskListener,
|
||||
Optional<FullscreenUnfoldController> appUnfoldTransitionController,
|
||||
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
|
||||
Optional<FreeformTaskListener> freeformTaskListener,
|
||||
Optional<FreeformTaskListener<?>> freeformTaskListener,
|
||||
Optional<RecentTasksController> recentTasksOptional,
|
||||
Transitions transitions,
|
||||
StartingWindowController startingWindow,
|
||||
|
||||
@@ -74,6 +74,8 @@ import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
|
||||
import com.android.wm.shell.unfold.UnfoldBackgroundController;
|
||||
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
|
||||
import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
|
||||
import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
|
||||
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -127,6 +129,26 @@ public class WMShellModule {
|
||||
taskViewTransitions, syncQueue);
|
||||
}
|
||||
|
||||
//
|
||||
// Window decoration
|
||||
//
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static WindowDecorViewModel<?> provideWindowDecorViewModel(
|
||||
Context context,
|
||||
@ShellMainThread Handler mainHandler,
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
DisplayController displayController,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
return new CaptionWindowDecorViewModel(
|
||||
context,
|
||||
mainHandler,
|
||||
taskOrganizer,
|
||||
displayController,
|
||||
syncQueue);
|
||||
}
|
||||
|
||||
//
|
||||
// Freeform
|
||||
//
|
||||
@@ -134,9 +156,10 @@ public class WMShellModule {
|
||||
@WMSingleton
|
||||
@Provides
|
||||
@DynamicOverride
|
||||
static FreeformTaskListener provideFreeformTaskListener(
|
||||
static FreeformTaskListener<?> provideFreeformTaskListener(
|
||||
WindowDecorViewModel<?> windowDecorViewModel,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
return new FreeformTaskListener(syncQueue);
|
||||
return new FreeformTaskListener<>(windowDecorViewModel, syncQueue);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -21,8 +21,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDO
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.provider.Settings;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
@@ -32,26 +30,35 @@ import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.protolog.ShellProtoLogGroup;
|
||||
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* {@link ShellTaskOrganizer.TaskListener} for {@link
|
||||
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
|
||||
*
|
||||
* @param <T> the type of window decoration instance
|
||||
*/
|
||||
public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
public class FreeformTaskListener<T extends AutoCloseable>
|
||||
implements ShellTaskOrganizer.TaskListener {
|
||||
private static final String TAG = "FreeformTaskListener";
|
||||
|
||||
private final WindowDecorViewModel<T> mWindowDecorationViewModel;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
private final SparseArray<State> mTasks = new SparseArray<>();
|
||||
private final SparseArray<State<T>> mTasks = new SparseArray<>();
|
||||
|
||||
private static class State {
|
||||
private static class State<T extends AutoCloseable> {
|
||||
RunningTaskInfo mTaskInfo;
|
||||
SurfaceControl mLeash;
|
||||
T mWindowDecoration;
|
||||
}
|
||||
|
||||
public FreeformTaskListener(SyncTransactionQueue syncQueue) {
|
||||
public FreeformTaskListener(
|
||||
WindowDecorViewModel<T> windowDecorationViewModel,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
mWindowDecorationViewModel = windowDecorationViewModel;
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
@@ -62,23 +69,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
}
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
|
||||
taskInfo.taskId);
|
||||
final State state = new State();
|
||||
final State<T> state = new State<>();
|
||||
state.mTaskInfo = taskInfo;
|
||||
state.mLeash = leash;
|
||||
state.mWindowDecoration =
|
||||
mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash);
|
||||
mTasks.put(taskInfo.taskId, state);
|
||||
|
||||
final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
|
||||
mSyncQueue.runInSync(t -> {
|
||||
Point taskPosition = taskInfo.positionInParent;
|
||||
t.setPosition(leash, taskPosition.x, taskPosition.y)
|
||||
.setWindowCrop(leash, taskBounds.width(), taskBounds.height())
|
||||
.show(leash);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskVanished(RunningTaskInfo taskInfo) {
|
||||
State state = mTasks.get(taskInfo.taskId);
|
||||
State<T> state = mTasks.get(taskInfo.taskId);
|
||||
if (state == null) {
|
||||
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
|
||||
return;
|
||||
@@ -86,11 +87,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
|
||||
taskInfo.taskId);
|
||||
mTasks.remove(taskInfo.taskId);
|
||||
|
||||
try {
|
||||
state.mWindowDecoration.close();
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to release window decoration.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
|
||||
State state = mTasks.get(taskInfo.taskId);
|
||||
State<T> state = mTasks.get(taskInfo.taskId);
|
||||
if (state == null) {
|
||||
throw new RuntimeException(
|
||||
"Task info changed before appearing: #" + taskInfo.taskId);
|
||||
@@ -98,15 +105,9 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
|
||||
taskInfo.taskId);
|
||||
state.mTaskInfo = taskInfo;
|
||||
|
||||
final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
|
||||
final SurfaceControl leash = state.mLeash;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
Point taskPosition = taskInfo.positionInParent;
|
||||
t.setPosition(leash, taskPosition.x, taskPosition.y)
|
||||
.setWindowCrop(leash, taskBounds.width(), taskBounds.height())
|
||||
.show(leash);
|
||||
});
|
||||
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
|
||||
taskInfo.taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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_UNDEFINED;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
/**
|
||||
* View model for the window decoration with a caption and shadows. Works with
|
||||
* {@link CaptionWindowDecoration}.
|
||||
*/
|
||||
public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
|
||||
private final ActivityTaskManager mActivityTaskManager;
|
||||
private final ShellTaskOrganizer mTaskOrganizer;
|
||||
private final Context mContext;
|
||||
private final Handler mMainHandler;
|
||||
private final DisplayController mDisplayController;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
public CaptionWindowDecorViewModel(
|
||||
Context context,
|
||||
Handler mainHandler,
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
DisplayController displayController,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
mContext = context;
|
||||
mMainHandler = mainHandler;
|
||||
mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
|
||||
mTaskOrganizer = taskOrganizer;
|
||||
mDisplayController = displayController;
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaptionWindowDecoration createWindowDecoration(
|
||||
ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface) {
|
||||
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
|
||||
mContext,
|
||||
mDisplayController,
|
||||
mTaskOrganizer,
|
||||
taskInfo,
|
||||
taskSurface,
|
||||
mMainHandler,
|
||||
mSyncQueue);
|
||||
TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
|
||||
CaptionTouchEventListener touchEventListener =
|
||||
new CaptionTouchEventListener(taskInfo, taskPositioner);
|
||||
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
|
||||
windowDecoration.setDragResizeCallback(taskPositioner);
|
||||
onTaskInfoChanged(taskInfo, windowDecoration);
|
||||
return windowDecoration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
|
||||
decoration.relayout(taskInfo);
|
||||
|
||||
int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
|
||||
decoration.setCaptionColor(statusBarColor);
|
||||
}
|
||||
|
||||
private class CaptionTouchEventListener implements
|
||||
View.OnClickListener, View.OnTouchListener {
|
||||
|
||||
private final int mTaskId;
|
||||
private final WindowContainerToken mTaskToken;
|
||||
private final DragResizeCallback mDragResizeCallback;
|
||||
|
||||
private int mDragPointerId = -1;
|
||||
|
||||
private CaptionTouchEventListener(
|
||||
RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) {
|
||||
mTaskId = taskInfo.taskId;
|
||||
mTaskToken = taskInfo.token;
|
||||
mDragResizeCallback = dragResizeCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int id = v.getId();
|
||||
if (id == R.id.close_window) {
|
||||
mActivityTaskManager.removeTask(mTaskId);
|
||||
} else if (id == R.id.maximize_window) {
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
|
||||
int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
|
||||
? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
|
||||
int displayWindowingMode =
|
||||
taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
|
||||
wct.setWindowingMode(mTaskToken,
|
||||
targetWindowingMode == displayWindowingMode
|
||||
? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
|
||||
if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
|
||||
wct.setBounds(mTaskToken, null);
|
||||
}
|
||||
mSyncQueue.queue(wct);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent e) {
|
||||
if (v.getId() != R.id.caption) {
|
||||
return false;
|
||||
}
|
||||
handleEventForMove(e);
|
||||
|
||||
if (e.getAction() != MotionEvent.ACTION_DOWN) {
|
||||
return false;
|
||||
}
|
||||
RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
|
||||
if (taskInfo.isFocused) {
|
||||
return false;
|
||||
}
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
wct.reorder(mTaskToken, true /* onTop */);
|
||||
mSyncQueue.queue(wct);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void handleEventForMove(MotionEvent e) {
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
mDragPointerId = e.getPointerId(0);
|
||||
mDragResizeCallback.onDragResizeStart(
|
||||
0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
|
||||
mDragResizeCallback.onDragResizeMove(
|
||||
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
|
||||
mDragResizeCallback.onDragResizeEnd(
|
||||
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.os.Handler;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.View;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
|
||||
/**
|
||||
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
|
||||
* {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
|
||||
*
|
||||
* {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
|
||||
* app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
|
||||
* caption buttons according to the luminance of the background.
|
||||
*
|
||||
* The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
|
||||
*/
|
||||
public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
|
||||
// The thickness of shadows of a window that has focus in DIP.
|
||||
private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
|
||||
// The thickness of shadows of a window that doesn't have focus in DIP.
|
||||
private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
|
||||
|
||||
// Height of button (32dp) + 2 * margin (5dp each)
|
||||
private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
|
||||
private static final int RESIZE_HANDLE_IN_DIP = 30;
|
||||
|
||||
private static final Rect EMPTY_OUTSET = new Rect();
|
||||
private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
|
||||
RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
|
||||
|
||||
private final Handler mHandler;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
|
||||
private View.OnClickListener mOnCaptionButtonClickListener;
|
||||
private View.OnTouchListener mOnCaptionTouchListener;
|
||||
private DragResizeCallback mDragResizeCallback;
|
||||
|
||||
private DragResizeInputListener mDragResizeListener;
|
||||
|
||||
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
|
||||
new WindowDecoration.RelayoutResult<>();
|
||||
|
||||
CaptionWindowDecoration(
|
||||
Context context,
|
||||
DisplayController displayController,
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
ActivityManager.RunningTaskInfo taskInfo,
|
||||
SurfaceControl taskSurface,
|
||||
Handler handler,
|
||||
SyncTransactionQueue syncQueue) {
|
||||
super(context, displayController, taskOrganizer, taskInfo, taskSurface);
|
||||
|
||||
mHandler = handler;
|
||||
mSyncQueue = syncQueue;
|
||||
}
|
||||
|
||||
void setCaptionListeners(
|
||||
View.OnClickListener onCaptionButtonClickListener,
|
||||
View.OnTouchListener onCaptionTouchListener) {
|
||||
mOnCaptionButtonClickListener = onCaptionButtonClickListener;
|
||||
mOnCaptionTouchListener = onCaptionTouchListener;
|
||||
}
|
||||
|
||||
void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
|
||||
mDragResizeCallback = dragResizeCallback;
|
||||
}
|
||||
|
||||
void relayout(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
final int shadowRadiusDp = taskInfo.isFocused
|
||||
? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
|
||||
final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
|
||||
== WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
|
||||
final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
|
||||
|
||||
WindowDecorLinearLayout oldRootView = mResult.mRootView;
|
||||
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
|
||||
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
|
||||
DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult);
|
||||
taskInfo = null; // Clear it just in case we use it accidentally
|
||||
|
||||
mSyncQueue.runInSync(transaction -> {
|
||||
transaction.merge(t);
|
||||
t.close();
|
||||
|
||||
mTaskOrganizer.applyTransaction(wct);
|
||||
});
|
||||
|
||||
if (mResult.mRootView == null) {
|
||||
// This means the task is hidden. Nothing is set up in this case including the
|
||||
// decoration surface.
|
||||
return;
|
||||
}
|
||||
if (oldRootView != mResult.mRootView) {
|
||||
setupRootView();
|
||||
}
|
||||
|
||||
if (!isDragResizeable) {
|
||||
closeDragResizeListener();
|
||||
return;
|
||||
}
|
||||
|
||||
if (oldDecorationSurface != mDecorationContainerSurface) {
|
||||
closeDragResizeListener();
|
||||
mDragResizeListener = new DragResizeInputListener(
|
||||
mContext,
|
||||
mHandler,
|
||||
mDisplay.getDisplayId(),
|
||||
mDecorationContainerSurface,
|
||||
mDragResizeCallback);
|
||||
}
|
||||
|
||||
mDragResizeListener.setGeometry(
|
||||
mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up listeners when a new root view is created.
|
||||
*/
|
||||
private void setupRootView() {
|
||||
View caption = mResult.mRootView.findViewById(R.id.caption);
|
||||
|
||||
caption.setOnTouchListener(mOnCaptionTouchListener);
|
||||
View maximize = caption.findViewById(R.id.maximize_window);
|
||||
maximize.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
View close = caption.findViewById(R.id.close_window);
|
||||
close.setOnClickListener(mOnCaptionButtonClickListener);
|
||||
}
|
||||
|
||||
void setCaptionColor(int captionColor) {
|
||||
if (mResult.mRootView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
View caption = mResult.mRootView.findViewById(R.id.caption);
|
||||
GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
|
||||
captionDrawable.setColor(captionColor);
|
||||
|
||||
int buttonTintColorRes =
|
||||
Color.valueOf(captionColor).luminance() < 0.5
|
||||
? R.color.decor_button_light_color
|
||||
: R.color.decor_button_dark_color;
|
||||
ColorStateList buttonTintColor =
|
||||
caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
|
||||
View maximize = caption.findViewById(R.id.maximize_window);
|
||||
VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
|
||||
maximizeBackground.setTintList(buttonTintColor);
|
||||
|
||||
View close = caption.findViewById(R.id.close_window);
|
||||
VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
|
||||
closeBackground.setTintList(buttonTintColor);
|
||||
}
|
||||
|
||||
private void closeDragResizeListener() {
|
||||
if (mDragResizeListener == null) {
|
||||
return;
|
||||
}
|
||||
mDragResizeListener.close();
|
||||
mDragResizeListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closeDragResizeListener();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
/**
|
||||
* Callback called when receiving drag-resize or drag-move related input events.
|
||||
*/
|
||||
public interface DragResizeCallback {
|
||||
/**
|
||||
* Called when a drag resize starts.
|
||||
*
|
||||
* @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
|
||||
* {@code 0} to indicate it's a move
|
||||
* @param x x coordinate in window decoration coordinate system where the drag resize starts
|
||||
* @param y y coordinate in window decoration coordinate system where the drag resize starts
|
||||
*/
|
||||
void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
|
||||
|
||||
/**
|
||||
* Called when the pointer moves during a drag resize.
|
||||
* @param x x coordinate in window decoration coordinate system of the new pointer location
|
||||
* @param y y coordinate in window decoration coordinate system of the new pointer location
|
||||
*/
|
||||
void onDragResizeMove(float x, float y);
|
||||
|
||||
/**
|
||||
* Called when a drag resize stops.
|
||||
* @param x x coordinate in window decoration coordinate system where the drag resize stops
|
||||
* @param y y coordinate in window decoration coordinate system where the drag resize stops
|
||||
*/
|
||||
void onDragResizeEnd(float x, float y);
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
|
||||
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.Region;
|
||||
import android.hardware.input.InputManager;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.view.IWindowSession;
|
||||
import android.view.InputChannel;
|
||||
import android.view.InputEvent;
|
||||
import android.view.InputEventReceiver;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
import com.android.internal.view.BaseIWindow;
|
||||
|
||||
/**
|
||||
* An input event listener registered to InputDispatcher to receive input events on task edges and
|
||||
* convert them to drag resize requests.
|
||||
*/
|
||||
class DragResizeInputListener implements AutoCloseable {
|
||||
private static final String TAG = "DragResizeInputListener";
|
||||
|
||||
private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
|
||||
private final Handler mHandler;
|
||||
private final InputManager mInputManager;
|
||||
|
||||
private final int mDisplayId;
|
||||
private final BaseIWindow mFakeWindow;
|
||||
private final IBinder mFocusGrantToken;
|
||||
private final SurfaceControl mDecorationSurface;
|
||||
private final InputChannel mInputChannel;
|
||||
private final TaskResizeInputEventReceiver mInputEventReceiver;
|
||||
private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mResizeHandleThickness;
|
||||
|
||||
private int mDragPointerId = -1;
|
||||
|
||||
DragResizeInputListener(
|
||||
Context context,
|
||||
Handler handler,
|
||||
int displayId,
|
||||
SurfaceControl decorationSurface,
|
||||
DragResizeCallback callback) {
|
||||
mInputManager = context.getSystemService(InputManager.class);
|
||||
mHandler = handler;
|
||||
mDisplayId = displayId;
|
||||
mDecorationSurface = decorationSurface;
|
||||
// Use a fake window as the backing surface is a container layer and we don't want to create
|
||||
// a buffer layer for it so we can't use ViewRootImpl.
|
||||
mFakeWindow = new BaseIWindow();
|
||||
mFakeWindow.setSession(mWindowSession);
|
||||
mFocusGrantToken = new Binder();
|
||||
mInputChannel = new InputChannel();
|
||||
try {
|
||||
mWindowSession.grantInputChannel(
|
||||
mDisplayId,
|
||||
new SurfaceControl(mDecorationSurface, TAG),
|
||||
mFakeWindow,
|
||||
null /* hostInputToken */,
|
||||
FLAG_NOT_FOCUSABLE,
|
||||
PRIVATE_FLAG_TRUSTED_OVERLAY,
|
||||
TYPE_APPLICATION,
|
||||
mFocusGrantToken,
|
||||
TAG + " of " + decorationSurface.toString(),
|
||||
mInputChannel);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
mInputEventReceiver = new TaskResizeInputEventReceiver(mInputChannel, mHandler.getLooper());
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates geometry of this drag resize handler. Needs to be called every time there is a size
|
||||
* change to notify the input event receiver it's ready to take the next input event. Otherwise
|
||||
* it'll keep batching move events and the drag resize process is stalled.
|
||||
*
|
||||
* This is also used to update the touch regions of this handler every event dispatched here is
|
||||
* a potential resize request.
|
||||
*
|
||||
* @param width The width of the drag resize handler in pixels, including resize handle
|
||||
* thickness. That is task width + 2 * resize handle thickness.
|
||||
* @param height The height of the drag resize handler in pixels, including resize handle
|
||||
* thickness. That is task height + 2 * resize handle thickness.
|
||||
* @param resizeHandleThickness The thickness of the resize handle in pixels.
|
||||
*/
|
||||
void setGeometry(int width, int height, int resizeHandleThickness) {
|
||||
if (mWidth == width && mHeight == height
|
||||
&& mResizeHandleThickness == resizeHandleThickness) {
|
||||
return;
|
||||
}
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mResizeHandleThickness = resizeHandleThickness;
|
||||
|
||||
Region touchRegion = new Region();
|
||||
final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
|
||||
touchRegion.union(topInputBounds);
|
||||
|
||||
final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
|
||||
mResizeHandleThickness, mHeight - mResizeHandleThickness);
|
||||
touchRegion.union(leftInputBounds);
|
||||
|
||||
final Rect rightInputBounds = new Rect(
|
||||
mWidth - mResizeHandleThickness, mResizeHandleThickness,
|
||||
mWidth, mHeight - mResizeHandleThickness);
|
||||
touchRegion.union(rightInputBounds);
|
||||
|
||||
final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
|
||||
mWidth, mHeight);
|
||||
touchRegion.union(bottomInputBounds);
|
||||
|
||||
try {
|
||||
mWindowSession.updateInputChannel(
|
||||
mInputChannel.getToken(),
|
||||
mDisplayId,
|
||||
new SurfaceControl(
|
||||
mDecorationSurface, "DragResizeInputListener#setTouchRegion"),
|
||||
FLAG_NOT_FOCUSABLE,
|
||||
PRIVATE_FLAG_TRUSTED_OVERLAY,
|
||||
touchRegion);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
// This marks all relevant components have handled the previous resize event and can take
|
||||
// the next one now.
|
||||
mInputEventReceiver.onHandledLastResizeEvent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mInputChannel.dispose();
|
||||
try {
|
||||
mWindowSession.remove(mFakeWindow);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private class TaskResizeInputEventReceiver extends InputEventReceiver {
|
||||
private boolean mWaitingForLastResizeEventHandled;
|
||||
|
||||
private TaskResizeInputEventReceiver(InputChannel inputChannel, Looper looper) {
|
||||
super(inputChannel, looper);
|
||||
}
|
||||
|
||||
private void onHandledLastResizeEvent() {
|
||||
mWaitingForLastResizeEventHandled = false;
|
||||
consumeBatchedInputEvents(-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchedInputEventPending(int source) {
|
||||
// InputEventReceiver keeps continuous move events in a batched event until explicitly
|
||||
// consuming it or an incompatible event shows up (likely an up event in this case). We
|
||||
// continue to keep move events in the next batched event until we receive a geometry
|
||||
// update so that we don't put too much pressure on the framework with excessive number
|
||||
// of input events if it can't handle them fast enough. It's more responsive to always
|
||||
// resize the task to the latest received coordinates.
|
||||
if (!mWaitingForLastResizeEventHandled) {
|
||||
consumeBatchedInputEvents(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputEvent(InputEvent inputEvent) {
|
||||
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
|
||||
}
|
||||
|
||||
private boolean handleInputEvent(InputEvent inputEvent) {
|
||||
if (!(inputEvent instanceof MotionEvent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MotionEvent e = (MotionEvent) inputEvent;
|
||||
switch (e.getActionMasked()) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mDragPointerId = e.getPointerId(0);
|
||||
mCallback.onDragResizeStart(
|
||||
calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
|
||||
mWaitingForLastResizeEventHandled = false;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
|
||||
mCallback.onDragResizeMove(
|
||||
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
|
||||
mWaitingForLastResizeEventHandled = true;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
|
||||
mCallback.onDragResizeEnd(
|
||||
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
|
||||
mWaitingForLastResizeEventHandled = false;
|
||||
mDragPointerId = -1;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_HOVER_ENTER:
|
||||
case MotionEvent.ACTION_HOVER_MOVE: {
|
||||
updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_HOVER_EXIT:
|
||||
mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@TaskPositioner.CtrlType
|
||||
private int calculateCtrlType(float x, float y) {
|
||||
int ctrlType = 0;
|
||||
if (x < mResizeHandleThickness) {
|
||||
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
|
||||
}
|
||||
if (x > mWidth - mResizeHandleThickness) {
|
||||
ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
|
||||
}
|
||||
if (y < mResizeHandleThickness) {
|
||||
ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
|
||||
}
|
||||
if (y > mHeight - mResizeHandleThickness) {
|
||||
ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
|
||||
}
|
||||
return ctrlType;
|
||||
}
|
||||
|
||||
private void updateCursorType(float x, float y) {
|
||||
@TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
|
||||
|
||||
int cursorType = PointerIcon.TYPE_DEFAULT;
|
||||
switch (ctrlType) {
|
||||
case TaskPositioner.CTRL_TYPE_LEFT:
|
||||
case TaskPositioner.CTRL_TYPE_RIGHT:
|
||||
cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
|
||||
break;
|
||||
case TaskPositioner.CTRL_TYPE_TOP:
|
||||
case TaskPositioner.CTRL_TYPE_BOTTOM:
|
||||
cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
|
||||
break;
|
||||
case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP:
|
||||
case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM:
|
||||
cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
|
||||
break;
|
||||
case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM:
|
||||
case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP:
|
||||
cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
|
||||
break;
|
||||
}
|
||||
mInputManager.setPointerIconType(cursorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
interface TaskFocusStateConsumer {
|
||||
void setTaskFocusState(boolean focused);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
|
||||
class TaskPositioner implements DragResizeCallback {
|
||||
|
||||
@IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
|
||||
@interface CtrlType {}
|
||||
|
||||
static final int CTRL_TYPE_LEFT = 1;
|
||||
static final int CTRL_TYPE_RIGHT = 2;
|
||||
static final int CTRL_TYPE_TOP = 4;
|
||||
static final int CTRL_TYPE_BOTTOM = 8;
|
||||
|
||||
private final ShellTaskOrganizer mTaskOrganizer;
|
||||
private final WindowDecoration mWindowDecoration;
|
||||
|
||||
private final Rect mTaskBoundsAtDragStart = new Rect();
|
||||
private final PointF mResizeStartPoint = new PointF();
|
||||
private final Rect mResizeTaskBounds = new Rect();
|
||||
|
||||
private int mCtrlType;
|
||||
|
||||
TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
|
||||
mTaskOrganizer = taskOrganizer;
|
||||
mWindowDecoration = windowDecoration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragResizeStart(int ctrlType, float x, float y) {
|
||||
mCtrlType = ctrlType;
|
||||
|
||||
mTaskBoundsAtDragStart.set(
|
||||
mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
|
||||
mResizeStartPoint.set(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragResizeMove(float x, float y) {
|
||||
changeBounds(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragResizeEnd(float x, float y) {
|
||||
changeBounds(x, y);
|
||||
|
||||
mCtrlType = 0;
|
||||
mTaskBoundsAtDragStart.setEmpty();
|
||||
mResizeStartPoint.set(0, 0);
|
||||
}
|
||||
|
||||
private void changeBounds(float x, float y) {
|
||||
float deltaX = x - mResizeStartPoint.x;
|
||||
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
|
||||
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
|
||||
mResizeTaskBounds.left += deltaX;
|
||||
}
|
||||
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
|
||||
mResizeTaskBounds.right += deltaX;
|
||||
}
|
||||
float deltaY = y - mResizeStartPoint.y;
|
||||
if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
|
||||
mResizeTaskBounds.top += deltaY;
|
||||
}
|
||||
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
|
||||
mResizeTaskBounds.bottom += deltaY;
|
||||
}
|
||||
if (mCtrlType == 0) {
|
||||
mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
|
||||
}
|
||||
|
||||
if (!mResizeTaskBounds.isEmpty()) {
|
||||
final WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
|
||||
mTaskOrganizer.applyTransaction(wct);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
|
||||
/**
|
||||
* A {@link LinearLayout} that takes an additional task focused drawable state. The new state is
|
||||
* used to select the correct background color for views in the window decoration.
|
||||
*/
|
||||
public class WindowDecorLinearLayout extends LinearLayout implements TaskFocusStateConsumer {
|
||||
private static final int[] TASK_FOCUSED_STATE = { R.attr.state_task_focused };
|
||||
|
||||
private boolean mIsTaskFocused;
|
||||
|
||||
public WindowDecorLinearLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public WindowDecorLinearLayout(Context context,
|
||||
@Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public WindowDecorLinearLayout(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public WindowDecorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskFocusState(boolean focused) {
|
||||
mIsTaskFocused = focused;
|
||||
|
||||
refreshDrawableState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] onCreateDrawableState(int extraSpace) {
|
||||
if (!mIsTaskFocused) {
|
||||
return super.onCreateDrawableState(extraSpace);
|
||||
}
|
||||
|
||||
final int[] states = super.onCreateDrawableState(extraSpace + 1);
|
||||
mergeDrawableStates(states, TASK_FOCUSED_STATE);
|
||||
return states;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
/**
|
||||
* The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
|
||||
* customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
|
||||
* interactions with UI widgets in window decorations and send corresponding requests to system
|
||||
* servers.
|
||||
*
|
||||
* @param <T> The actual decoration type
|
||||
*/
|
||||
public interface WindowDecorViewModel<T extends AutoCloseable> {
|
||||
|
||||
/**
|
||||
* Creates a window decoration for the given task.
|
||||
*
|
||||
* @param taskInfo the initial task info of the task
|
||||
* @param taskSurface the surface of the task
|
||||
* @return the window decoration object
|
||||
*/
|
||||
T createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface);
|
||||
|
||||
/**
|
||||
* Notifies a task info update on the given task, with the window decoration created previously
|
||||
* for this task by {@link #createWindowDecoration}.
|
||||
*
|
||||
* @param taskInfo the new task info of the task
|
||||
* @param windowDecoration the window decoration created for the task
|
||||
*/
|
||||
void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.InsetsState;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceControlViewHost;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowlessWindowManager;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
|
||||
/**
|
||||
* Manages a container surface and a windowless window to show window decoration. Responsible to
|
||||
* update window decoration window state and layout parameters on task info changes and so that
|
||||
* window decoration is in correct state and bounds.
|
||||
*
|
||||
* The container surface is a child of the task display area in the same display, so that window
|
||||
* decorations can be drawn out of the task bounds and receive input events from out of the task
|
||||
* bounds to support drag resizing.
|
||||
*
|
||||
* The windowless window that hosts window decoration is positioned in front of all activities, to
|
||||
* allow the foreground activity to draw its own background behind window decorations, such as
|
||||
* the window captions.
|
||||
*
|
||||
* @param <T> The type of the root view
|
||||
*/
|
||||
public class WindowDecoration<T extends View & TaskFocusStateConsumer> implements AutoCloseable {
|
||||
private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR };
|
||||
|
||||
/**
|
||||
* System-wide context. Only used to create context with overridden configurations.
|
||||
*/
|
||||
final Context mContext;
|
||||
final DisplayController mDisplayController;
|
||||
final ShellTaskOrganizer mTaskOrganizer;
|
||||
|
||||
RunningTaskInfo mTaskInfo;
|
||||
final SurfaceControl mTaskSurface;
|
||||
|
||||
Display mDisplay;
|
||||
Context mDecorWindowContext;
|
||||
SurfaceControl mDecorationContainerSurface;
|
||||
SurfaceControl mTaskBackgroundSurface;
|
||||
|
||||
private CaptionWindowManager mCaptionWindowManager;
|
||||
private SurfaceControlViewHost mViewHost;
|
||||
|
||||
private final Rect mCaptionInsetsRect = new Rect();
|
||||
private final Rect mTaskSurfaceCrop = new Rect();
|
||||
private final float[] mTmpColor = new float[3];
|
||||
|
||||
WindowDecoration(
|
||||
Context context,
|
||||
DisplayController displayController,
|
||||
ShellTaskOrganizer taskOrganizer,
|
||||
RunningTaskInfo taskInfo,
|
||||
SurfaceControl taskSurface) {
|
||||
mContext = context;
|
||||
mDisplayController = displayController;
|
||||
mTaskOrganizer = taskOrganizer;
|
||||
mTaskInfo = taskInfo;
|
||||
mTaskSurface = taskSurface;
|
||||
|
||||
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
|
||||
mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
|
||||
|
||||
// Put caption under task surface because ViewRootImpl sets the destination frame of
|
||||
// windowless window layers and BLASTBufferQueue#update() doesn't support offset.
|
||||
mCaptionWindowManager =
|
||||
new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface);
|
||||
}
|
||||
|
||||
void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
|
||||
Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t,
|
||||
WindowContainerTransaction wct, RelayoutResult<T> outResult) {
|
||||
outResult.reset();
|
||||
|
||||
final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
|
||||
if (taskInfo != null) {
|
||||
mTaskInfo = taskInfo;
|
||||
}
|
||||
|
||||
if (!mTaskInfo.isVisible) {
|
||||
close();
|
||||
t.hide(mTaskSurface);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rootView == null && layoutResId == 0) {
|
||||
throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
|
||||
}
|
||||
|
||||
outResult.mRootView = rootView;
|
||||
rootView = null; // Clear it just in case we use it accidentally
|
||||
final Configuration taskConfig = mTaskInfo.getConfiguration();
|
||||
if (oldTaskConfig.densityDpi != taskConfig.densityDpi
|
||||
|| mDisplay.getDisplayId() != mTaskInfo.displayId) {
|
||||
close();
|
||||
|
||||
mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
|
||||
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
|
||||
if (layoutResId != 0) {
|
||||
outResult.mRootView =
|
||||
(T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (outResult.mRootView == null) {
|
||||
outResult.mRootView =
|
||||
(T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
|
||||
}
|
||||
|
||||
// DecorationContainerSurface
|
||||
if (mDecorationContainerSurface == null) {
|
||||
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
|
||||
mDecorationContainerSurface = builder
|
||||
.setName("Decor container of Task=" + mTaskInfo.taskId)
|
||||
.setContainerLayer()
|
||||
.setParent(mTaskSurface)
|
||||
.build();
|
||||
|
||||
t.setTrustedOverlay(mDecorationContainerSurface, true);
|
||||
}
|
||||
|
||||
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
|
||||
outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
|
||||
final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
|
||||
final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
|
||||
outResult.mWidth = taskBounds.width()
|
||||
+ (int) (outsetsDp.right * outResult.mDensity)
|
||||
- decorContainerOffsetX;
|
||||
outResult.mHeight = taskBounds.height()
|
||||
+ (int) (outsetsDp.bottom * outResult.mDensity)
|
||||
- decorContainerOffsetY;
|
||||
t.setPosition(mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
|
||||
.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
|
||||
.setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
|
||||
.show(mDecorationContainerSurface);
|
||||
|
||||
// TaskBackgroundSurface
|
||||
if (mTaskBackgroundSurface == null) {
|
||||
final SurfaceControl.Builder builder = new SurfaceControl.Builder();
|
||||
mTaskBackgroundSurface = builder
|
||||
.setName("Background of Task=" + mTaskInfo.taskId)
|
||||
.setEffectLayer()
|
||||
.setParent(mTaskSurface)
|
||||
.build();
|
||||
}
|
||||
|
||||
float shadowRadius = outResult.mDensity * shadowRadiusDp;
|
||||
int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
|
||||
mTmpColor[0] = Color.red(backgroundColorInt);
|
||||
mTmpColor[1] = Color.green(backgroundColorInt);
|
||||
mTmpColor[2] = Color.blue(backgroundColorInt);
|
||||
t.setCrop(mTaskBackgroundSurface, taskBounds)
|
||||
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
|
||||
.setColor(mTaskBackgroundSurface, mTmpColor);
|
||||
|
||||
// Caption view
|
||||
mCaptionWindowManager.setConfiguration(taskConfig);
|
||||
final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
|
||||
final WindowManager.LayoutParams lp =
|
||||
new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
|
||||
WindowManager.LayoutParams.TYPE_APPLICATION,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
|
||||
lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
|
||||
lp.setTrustedOverlay();
|
||||
if (mViewHost == null) {
|
||||
mViewHost = new SurfaceControlViewHost(mDecorWindowContext, mDisplay,
|
||||
mCaptionWindowManager, true);
|
||||
mViewHost.setView(outResult.mRootView, lp);
|
||||
} else {
|
||||
mViewHost.relayout(lp);
|
||||
}
|
||||
|
||||
outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
|
||||
|
||||
// Caption insets
|
||||
mCaptionInsetsRect.set(taskBounds);
|
||||
mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
|
||||
wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
|
||||
|
||||
// Task surface itself
|
||||
Point taskPosition = mTaskInfo.positionInParent;
|
||||
mTaskSurfaceCrop.set(
|
||||
decorContainerOffsetX,
|
||||
decorContainerOffsetY,
|
||||
outResult.mWidth + decorContainerOffsetX,
|
||||
outResult.mHeight + decorContainerOffsetY);
|
||||
t.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
|
||||
.setCrop(mTaskSurface, mTaskSurfaceCrop)
|
||||
.show(mTaskSurface);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (mViewHost != null) {
|
||||
mViewHost.release();
|
||||
mViewHost = null;
|
||||
}
|
||||
|
||||
if (mDecorationContainerSurface != null) {
|
||||
mDecorationContainerSurface.release();
|
||||
mDecorationContainerSurface = null;
|
||||
}
|
||||
|
||||
if (mTaskBackgroundSurface != null) {
|
||||
mTaskBackgroundSurface.release();
|
||||
mTaskBackgroundSurface = null;
|
||||
}
|
||||
}
|
||||
|
||||
static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
float mDensity;
|
||||
T mRootView;
|
||||
|
||||
void reset() {
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
mDensity = 0;
|
||||
mRootView = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CaptionWindowManager extends WindowlessWindowManager {
|
||||
CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
|
||||
super(config, rootSurface, null /* hostInputToken */);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setConfiguration(Configuration configuration) {
|
||||
super.setConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4880,9 +4880,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove
|
||||
// the special case when it does.
|
||||
if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
|
||||
if (task.isOrganized()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -288,10 +288,7 @@ final class InputMonitor {
|
||||
SurfaceControl touchableRegionCrop = null;
|
||||
final Task task = w.getTask();
|
||||
if (task != null) {
|
||||
// TODO(b/165794636): Remove the special case for freeform window once drag resizing is
|
||||
// handled by WM shell.
|
||||
if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
|
||||
&& !task.inFreeformWindowingMode()) {
|
||||
if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
|
||||
// If the window is in a TaskManaged by a TaskOrganizer then most cropping will
|
||||
// be applied using the SurfaceControl hierarchy from the Organizer. This means
|
||||
// we need to make sure that these changes in crop are reflected in the input
|
||||
|
||||
@@ -69,8 +69,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
|
||||
|
||||
import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
|
||||
import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
|
||||
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
|
||||
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
|
||||
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
|
||||
@@ -131,7 +129,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
|
||||
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
|
||||
import static com.android.server.wm.WindowManagerService.dipToPixel;
|
||||
|
||||
import static java.lang.Integer.MAX_VALUE;
|
||||
|
||||
@@ -3277,7 +3274,6 @@ class Task extends TaskFragment {
|
||||
}
|
||||
|
||||
final SurfaceControl.Transaction t = getSyncTransaction();
|
||||
updateShadowsRadius(isFocused(), t);
|
||||
|
||||
if (mDimmer.updateDims(t, mTmpDimBoundsRect)) {
|
||||
scheduleAnimation();
|
||||
@@ -4248,49 +4244,11 @@ class Task extends TaskFragment {
|
||||
return getActivity(ActivityRecord::isVisible) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the desired shadow radius in pixels for the current task.
|
||||
*/
|
||||
private float getShadowRadius(boolean taskIsFocused) {
|
||||
int elevation = 0;
|
||||
|
||||
// Get elevation for a specific windowing mode.
|
||||
if (inFreeformWindowingMode()) {
|
||||
elevation = taskIsFocused
|
||||
? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
|
||||
} else {
|
||||
// For all other windowing modes, do not draw a shadow.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If the task has no visible children, do not draw a shadow.
|
||||
if (!hasVisibleChildren()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return dipToPixel(elevation, getDisplayContent().getDisplayMetrics());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the length of the shadow if needed based on windowing mode and task focus state.
|
||||
*/
|
||||
private void updateShadowsRadius(boolean taskIsFocused,
|
||||
SurfaceControl.Transaction pendingTransaction) {
|
||||
if (!isRootTask()) return;
|
||||
|
||||
final float newShadowRadius = getShadowRadius(taskIsFocused);
|
||||
if (mShadowRadius != newShadowRadius) {
|
||||
mShadowRadius = newShadowRadius;
|
||||
pendingTransaction.setShadowRadius(getSurfaceControl(), mShadowRadius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on the task when it gained or lost focus.
|
||||
* @param hasFocus
|
||||
*/
|
||||
void onAppFocusChanged(boolean hasFocus) {
|
||||
updateShadowsRadius(hasFocus, getSyncTransaction());
|
||||
dispatchTaskInfoChangedIfNeeded(false /* force */);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user