Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −0 Original line number Diff line number Diff line Loading @@ -245,7 +245,10 @@ public abstract class WMShellModule { return new CaptionWindowDecorViewModel( context, mainHandler, mainExecutor, mainChoreographer, windowManager, shellInit, taskOrganizer, displayController, rootTaskDisplayAreaOrganizer, Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +104 −2 Original line number Diff line number Diff line Loading @@ -26,12 +26,20 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import android.app.ActivityManager.RunningTaskInfo; import android.content.ContentResolver; import android.content.Context; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Handler; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; import android.view.Display; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; Loading @@ -45,39 +53,74 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** * View model for the window decoration with a caption and shadows. Works with * {@link CaptionWindowDecoration}. */ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private static final String TAG = "CaptionWindowDecorViewModel"; private final ShellTaskOrganizer mTaskOrganizer; private final IWindowManager mWindowManager; private final Context mContext; private final Handler mMainHandler; private final ShellExecutor mMainExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final SyncTransactionQueue mSyncQueue; private final Transitions mTransitions; private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; private TaskOperations mTaskOperations; /** * Whether to pilfer the next motion event to send cancellations to the windows below. * Useful when the caption window is spy and the gesture should be handled by the system * instead of by the app for their custom header content. */ private boolean mShouldPilferCaptionEvents; private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @Override public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) { if (mContext.getDisplayId() != displayId) { return; } mMainExecutor.execute(() -> { mExclusionRegion.set(systemGestureExclusion); }); } }; public CaptionWindowDecorViewModel( Context context, Handler mainHandler, ShellExecutor shellExecutor, Choreographer mainChoreographer, IWindowManager windowManager, ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, Transitions transitions) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; mWindowManager = windowManager; mMainChoreographer = mainChoreographer; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; Loading @@ -87,6 +130,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } mInputManager = mContext.getSystemService(InputManager.class); shellInit.addInitCallback(this::onInit, this); } private void onInit() { try { mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener, mContext.getDisplayId()); } catch (RemoteException e) { Log.e(TAG, "Failed to register window manager callbacks", e); } } @Override Loading Loading @@ -178,9 +233,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { decoration.setCaptionColor(Color.TRANSPARENT); } else { final int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); decoration.setCaptionColor(statusBarColor); } } private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { Loading Loading @@ -301,6 +360,49 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue.queue(wct); } } final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int actionMasked = e.getActionMasked(); final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN; final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL || actionMasked == MotionEvent.ACTION_UP; if (isDown) { final boolean downInCustomizableCaptionRegion = decoration.checkTouchEventInCustomizableRegion(e); final boolean downInExclusionRegion = mExclusionRegion.contains( (int) e.getRawX(), (int) e.getRawY()); final boolean isTransparentCaption = TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo); // MotionEvent's coordinates are relative to view, we want location in window // to offset position relative to caption as a whole. int[] viewLocation = new int[2]; v.getLocationInWindow(viewLocation); final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e, new Point(viewLocation[0], viewLocation[1])); // The caption window may be a spy window when the caption background is // transparent, which means events will fall through to the app window. Make // sure to cancel these events if they do not happen in the intersection of the // customizable region and what the app reported as exclusion areas, because // the drag-move or other caption gestures should take priority outside those // regions. mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion && downInExclusionRegion && isTransparentCaption) && !isResizeEvent; } if (!mShouldPilferCaptionEvents) { // The event will be handled by a window below or pilfered by resize handler. return false; } // Otherwise pilfer so that windows below receive cancellations for this gesture, and // continue normal handling as a caption gesture. if (mInputManager != null) { // TODO(b/352127475): Only pilfer once per gesture mInputManager.pilferPointers(v.getViewRootImpl().getInputToken()); } if (isUpOrCancel) { // Gesture is finished, reset state. mShouldPilferCaptionEvents = false; } return mDragDetector.onMotionEvent(e); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +58 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; Loading @@ -28,22 +29,27 @@ 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.GradientDrawable; import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.util.Size; import android.view.Choreographer; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManager; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; 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.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with Loading Loading @@ -177,12 +183,44 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL shouldSetTaskPositionAndCrop); } @VisibleForTesting static void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mShadowRadiusId = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall // through to the windows below so that the app can respond to input events on // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } final RelayoutParams.OccludingCaptionElement backButtonElement = new RelayoutParams.OccludingCaptionElement(); backButtonElement.mWidthResId = R.dimen.caption_left_buttons_width; backButtonElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START; relayoutParams.mOccludingCaptionElements.add(backButtonElement); // Then, the right-aligned section (minimize, maximize and close buttons). final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); controlsElement.mWidthResId = R.dimen.caption_right_buttons_width; controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); } void relayout(RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { final int shadowRadiusID = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; final boolean isFreeform = taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; Loading @@ -191,13 +229,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode()); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw, setTaskCropAndPosition); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo Loading Loading @@ -303,6 +336,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDragResizeListener = null; } /** * Checks whether the touch event falls inside the customizable caption region. */ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) { return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY()); } boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) { return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset); } @Override public void close() { closeDragResizeListener(); Loading @@ -311,6 +355,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL @Override int getCaptionHeightId(@WindowingMode int windowingMode) { return getCaptionHeightIdStatic(windowingMode); } private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.ComponentName import android.testing.AndroidTestingRunner import android.view.Display import android.view.WindowInsetsController import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class CaptionWindowDecorationTests : ShellTestCase() { @Test fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { val taskInfo = createTaskInfo() taskInfo.configuration.windowConfiguration.windowingMode = WindowConfiguration.WINDOWING_MODE_FREEFORM taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue() } @Test fun updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() { val taskInfo = createTaskInfo() taskInfo.configuration.windowConfiguration.windowingMode = WindowConfiguration.WINDOWING_MODE_FREEFORM taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = 0 val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse() } @Test fun updateRelayoutParams_addOccludingCaptionElementCorrectly() { val taskInfo = createTaskInfo() val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2) Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo( WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.START) Truth.assertThat(relayoutParams.mOccludingCaptionElements[1].mAlignment).isEqualTo( WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.END) } private fun createTaskInfo(): ActivityManager.RunningTaskInfo { val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder() val taskInfo = TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) .setTaskDescriptionBuilder(taskDescriptionBuilder) .setVisible(true) .build() taskInfo.realActivity = ComponentName( "com.android.wm.shell.windowdecor", "CaptionWindowDecorationTests" ) taskInfo.baseActivity = ComponentName( "com.android.wm.shell.windowdecor", "CaptionWindowDecorationTests" ) return taskInfo } } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +3 −0 Original line number Diff line number Diff line Loading @@ -245,7 +245,10 @@ public abstract class WMShellModule { return new CaptionWindowDecorViewModel( context, mainHandler, mainExecutor, mainChoreographer, windowManager, shellInit, taskOrganizer, displayController, rootTaskDisplayAreaOrganizer, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +104 −2 Original line number Diff line number Diff line Loading @@ -26,12 +26,20 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import android.app.ActivityManager.RunningTaskInfo; import android.content.ContentResolver; import android.content.Context; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.hardware.input.InputManager; import android.os.Handler; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; import android.util.SparseArray; import android.view.Choreographer; import android.view.Display; import android.view.ISystemGestureExclusionListener; import android.view.IWindowManager; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; Loading @@ -45,39 +53,74 @@ import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** * View model for the window decoration with a caption and shadows. Works with * {@link CaptionWindowDecoration}. */ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private static final String TAG = "CaptionWindowDecorViewModel"; private final ShellTaskOrganizer mTaskOrganizer; private final IWindowManager mWindowManager; private final Context mContext; private final Handler mMainHandler; private final ShellExecutor mMainExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final SyncTransactionQueue mSyncQueue; private final Transitions mTransitions; private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; private TaskOperations mTaskOperations; /** * Whether to pilfer the next motion event to send cancellations to the windows below. * Useful when the caption window is spy and the gesture should be handled by the system * instead of by the app for their custom header content. */ private boolean mShouldPilferCaptionEvents; private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); private final ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @Override public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) { if (mContext.getDisplayId() != displayId) { return; } mMainExecutor.execute(() -> { mExclusionRegion.set(systemGestureExclusion); }); } }; public CaptionWindowDecorViewModel( Context context, Handler mainHandler, ShellExecutor shellExecutor, Choreographer mainChoreographer, IWindowManager windowManager, ShellInit shellInit, ShellTaskOrganizer taskOrganizer, DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, Transitions transitions) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; mWindowManager = windowManager; mMainChoreographer = mainChoreographer; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; Loading @@ -87,6 +130,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } mInputManager = mContext.getSystemService(InputManager.class); shellInit.addInitCallback(this::onInit, this); } private void onInit() { try { mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener, mContext.getDisplayId()); } catch (RemoteException e) { Log.e(TAG, "Failed to register window manager callbacks", e); } } @Override Loading Loading @@ -178,9 +233,13 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } private void setupCaptionColor(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { decoration.setCaptionColor(Color.TRANSPARENT); } else { final int statusBarColor = taskInfo.taskDescription.getStatusBarColor(); decoration.setCaptionColor(statusBarColor); } } private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) { Loading Loading @@ -301,6 +360,49 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue.queue(wct); } } final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final int actionMasked = e.getActionMasked(); final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN; final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL || actionMasked == MotionEvent.ACTION_UP; if (isDown) { final boolean downInCustomizableCaptionRegion = decoration.checkTouchEventInCustomizableRegion(e); final boolean downInExclusionRegion = mExclusionRegion.contains( (int) e.getRawX(), (int) e.getRawY()); final boolean isTransparentCaption = TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo); // MotionEvent's coordinates are relative to view, we want location in window // to offset position relative to caption as a whole. int[] viewLocation = new int[2]; v.getLocationInWindow(viewLocation); final boolean isResizeEvent = decoration.shouldResizeListenerHandleEvent(e, new Point(viewLocation[0], viewLocation[1])); // The caption window may be a spy window when the caption background is // transparent, which means events will fall through to the app window. Make // sure to cancel these events if they do not happen in the intersection of the // customizable region and what the app reported as exclusion areas, because // the drag-move or other caption gestures should take priority outside those // regions. mShouldPilferCaptionEvents = !(downInCustomizableCaptionRegion && downInExclusionRegion && isTransparentCaption) && !isResizeEvent; } if (!mShouldPilferCaptionEvents) { // The event will be handled by a window below or pilfered by resize handler. return false; } // Otherwise pilfer so that windows below receive cancellations for this gesture, and // continue normal handling as a caption gesture. if (mInputManager != null) { // TODO(b/352127475): Only pilfer once per gesture mInputManager.pilferPointers(v.getViewRootImpl().getInputToken()); } if (isUpOrCancel) { // Gesture is finished, reset state. mShouldPilferCaptionEvents = false; } return mDragDetector.onMotionEvent(e); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +58 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; Loading @@ -28,22 +29,27 @@ 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.GradientDrawable; import android.graphics.drawable.VectorDrawable; import android.os.Handler; import android.util.Size; import android.view.Choreographer; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.View; import android.view.ViewConfiguration; import android.view.WindowManager; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; 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.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with Loading Loading @@ -177,12 +183,44 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL shouldSetTaskPositionAndCrop); } @VisibleForTesting static void updateRelayoutParams( RelayoutParams relayoutParams, ActivityManager.RunningTaskInfo taskInfo, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = R.layout.caption_window_decor; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mShadowRadiusId = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall // through to the windows below so that the app can respond to input events on // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } final RelayoutParams.OccludingCaptionElement backButtonElement = new RelayoutParams.OccludingCaptionElement(); backButtonElement.mWidthResId = R.dimen.caption_left_buttons_width; backButtonElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START; relayoutParams.mOccludingCaptionElements.add(backButtonElement); // Then, the right-aligned section (minimize, maximize and close buttons). final RelayoutParams.OccludingCaptionElement controlsElement = new RelayoutParams.OccludingCaptionElement(); controlsElement.mWidthResId = R.dimen.caption_right_buttons_width; controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); } void relayout(RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) { final int shadowRadiusID = taskInfo.isFocused ? R.dimen.freeform_decor_shadow_focused_thickness : R.dimen.freeform_decor_shadow_unfocused_thickness; final boolean isFreeform = taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM; final boolean isDragResizeable = isFreeform && taskInfo.isResizeable; Loading @@ -191,13 +229,8 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode()); mRelayoutParams.mShadowRadiusId = shadowRadiusID; mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition; updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw, setTaskCropAndPosition); relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo Loading Loading @@ -303,6 +336,17 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL mDragResizeListener = null; } /** * Checks whether the touch event falls inside the customizable caption region. */ boolean checkTouchEventInCustomizableRegion(MotionEvent ev) { return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY()); } boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) { return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset); } @Override public void close() { closeDragResizeListener(); Loading @@ -311,6 +355,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL @Override int getCaptionHeightId(@WindowingMode int windowingMode) { return getCaptionHeightIdStatic(windowingMode); } private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) { return R.dimen.freeform_decor_caption_height; } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorationTests.kt 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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.ComponentName import android.testing.AndroidTestingRunner import android.view.Display import android.view.WindowInsetsController import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder import com.google.common.truth.Truth import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) class CaptionWindowDecorationTests : ShellTestCase() { @Test fun updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { val taskInfo = createTaskInfo() taskInfo.configuration.windowConfiguration.windowingMode = WindowConfiguration.WINDOWING_MODE_FREEFORM taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isTrue() } @Test fun updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() { val taskInfo = createTaskInfo() taskInfo.configuration.windowConfiguration.windowingMode = WindowConfiguration.WINDOWING_MODE_FREEFORM taskInfo.taskDescription!!.topOpaqueSystemBarsAppearance = 0 val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.hasInputFeatureSpy()).isFalse() } @Test fun updateRelayoutParams_addOccludingCaptionElementCorrectly() { val taskInfo = createTaskInfo() val relayoutParams = WindowDecoration.RelayoutParams() CaptionWindowDecoration.updateRelayoutParams( relayoutParams, taskInfo, true, false ) Truth.assertThat(relayoutParams.mOccludingCaptionElements.size).isEqualTo(2) Truth.assertThat(relayoutParams.mOccludingCaptionElements[0].mAlignment).isEqualTo( WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.START) Truth.assertThat(relayoutParams.mOccludingCaptionElements[1].mAlignment).isEqualTo( WindowDecoration.RelayoutParams.OccludingCaptionElement.Alignment.END) } private fun createTaskInfo(): ActivityManager.RunningTaskInfo { val taskDescriptionBuilder = ActivityManager.TaskDescription.Builder() val taskInfo = TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) .setTaskDescriptionBuilder(taskDescriptionBuilder) .setVisible(true) .build() taskInfo.realActivity = ComponentName( "com.android.wm.shell.windowdecor", "CaptionWindowDecorationTests" ) taskInfo.baseActivity = ComponentName( "com.android.wm.shell.windowdecor", "CaptionWindowDecorationTests" ) return taskInfo } }