diff options
| author | 2023-06-19 17:03:28 +0000 | |
|---|---|---|
| committer | 2023-08-15 08:21:04 +0000 | |
| commit | 17b28d345e50acad5500ded93d43f1afc463b3e5 (patch) | |
| tree | 5dc271b1ff2463a5806f02f386253a6d3cc80997 | |
| parent | 33158ac73957dbb2911f6d6fc2df8c52546a5947 (diff) | |
[4/n] Add User Aspect Ratio Button
Adds the compat ui button which allows users to reach the app info
page in settings and change the aspect ratio for a selected app.
When the feature is enabled, the button appears every time the app is
launched and when TaskInfo.topActivityEligibleForUserAspectRatioButton
and TaskInfo.topActivityBoundsLetterboxed are both true.
The icons appears after a short interval and disappears after 4 seconds.
Fix: 287460588
Test: `atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest`
`atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest`
`atest WMShellUnitTests:CompatUIControllerTest`
`atest WMShellUnitTests:ReachabilityEduWindowManagerTest`
`atest WmTests:LetterboxUiControllerTest`
Change-Id: If1ed5442359b0bc3f352d3b2f9678a5921c73114
20 files changed, 1214 insertions, 77 deletions
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 2b5175ca6659..634089b73618 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -248,6 +248,13 @@ public class TaskInfo { public boolean topActivityEligibleForUserAspectRatioButton; /** + * Whether the user has forced the activity to be fullscreen through the user aspect ratio + * settings. + * @hide + */ + public boolean isUserFullscreenOverrideEnabled; + + /** * Hint about the letterbox state of the top activity. * @hide */ @@ -543,7 +550,8 @@ public class TaskInfo { && isSleeping == that.isSleeping && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId) && parentTaskId == that.parentTaskId - && Objects.equals(topActivity, that.topActivity); + && Objects.equals(topActivity, that.topActivity) + && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled; } /** @@ -574,7 +582,8 @@ public class TaskInfo { && (!hasCompatUI() || configuration.getLayoutDirection() == that.configuration.getLayoutDirection()) && (!hasCompatUI() || configuration.uiMode == that.configuration.uiMode) - && (!hasCompatUI() || isVisible == that.isVisible); + && (!hasCompatUI() || isVisible == that.isVisible) + && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled; } /** @@ -630,6 +639,7 @@ public class TaskInfo { topActivityLetterboxHorizontalPosition = source.readInt(); topActivityLetterboxWidth = source.readInt(); topActivityLetterboxHeight = source.readInt(); + isUserFullscreenOverrideEnabled = source.readBoolean(); } /** @@ -686,6 +696,7 @@ public class TaskInfo { dest.writeInt(topActivityLetterboxHorizontalPosition); dest.writeInt(topActivityLetterboxWidth); dest.writeInt(topActivityLetterboxHeight); + dest.writeBoolean(isUserFullscreenOverrideEnabled); } @Override @@ -732,6 +743,7 @@ public class TaskInfo { + topActivityLetterboxHorizontalPosition + " topActivityLetterboxWidth=" + topActivityLetterboxWidth + " topActivityLetterboxHeight=" + topActivityLetterboxHeight + + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled + " locusId=" + mTopActivityLocusId + " displayAreaFeatureId=" + displayAreaFeatureId + " cameraCompatControlState=" diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml new file mode 100644 index 000000000000..6e4752c9d27d --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48"> + <path + android:fillColor="@color/compat_controls_background" + android:strokeAlpha="0.8" + android:fillAlpha="0.8" + android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/> + <group + android:translateX="12" + android:translateY="12"> + <path + android:fillColor="@color/compat_controls_text" + android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/> + </group> +</vector> diff --git a/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml new file mode 100644 index 000000000000..141a1ce60b8e --- /dev/null +++ b/libs/WindowManager/Shell/res/drawable/user_aspect_ratio_settings_button_ripple.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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. + --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/compat_background_ripple"> + <item android:drawable="@drawable/user_aspect_ratio_settings_button"/> +</ripple>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml index dfaeeeb81c07..257fe1544bbb 100644 --- a/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml +++ b/libs/WindowManager/Shell/res/layout/compat_ui_layout.xml @@ -55,7 +55,7 @@ <include android:id="@+id/size_compat_hint" android:visibility="gone" - android:layout_width="@dimen/size_compat_hint_width" + android:layout_width="@dimen/compat_hint_width" android:layout_height="wrap_content" layout="@layout/compat_mode_hint"/> diff --git a/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml new file mode 100644 index 000000000000..433d8546ece0 --- /dev/null +++ b/libs/WindowManager/Shell/res/layout/user_aspect_ratio_settings_layout.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 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.compatui.UserAspectRatioSettingsLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="bottom|end"> + + <include android:id="@+id/user_aspect_ratio_settings_hint" + android:visibility="gone" + android:layout_width="@dimen/compat_hint_width" + android:layout_height="wrap_content" + layout="@layout/compat_mode_hint"/> + + <ImageButton + android:id="@+id/user_aspect_ratio_settings_button" + android:visibility="gone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/compat_button_margin" + android:layout_marginBottom="@dimen/compat_button_margin" + android:src="@drawable/user_aspect_ratio_settings_button_ripple" + android:background="@android:color/transparent" + android:contentDescription="@string/user_aspect_ratio_settings_button_description"/> + +</com.android.wm.shell.compatui.UserAspectRatioSettingsLayout> diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 64fed1cacca9..0502a99d2a45 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -260,8 +260,8 @@ + compat_button_margin - compat_hint_corner_radius - compat_hint_point_width / 2). --> <dimen name="compat_hint_padding_end">7dp</dimen> - <!-- The width of the size compat hint. --> - <dimen name="size_compat_hint_width">188dp</dimen> + <!-- The width of the compat hint. --> + <dimen name="compat_hint_width">188dp</dimen> <!-- The width of the camera compat hint. --> <dimen name="camera_compat_hint_width">143dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index b192fdf245e2..8cbc3d016b01 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -173,7 +173,13 @@ <string name="accessibility_bubble_dismissed">Bubble dismissed.</string> <!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] --> - <string name="restart_button_description">Tap to restart this app for a better view.</string> + <string name="restart_button_description">Tap to restart this app for a better view</string> + + <!-- Tooltip text of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] --> + <string name="user_aspect_ratio_settings_button_hint">Change this app\'s aspect ratio in Settings</string> + + <!-- Content description of the button for the user aspect ratio settings. [CHAR LIMIT=NONE] --> + <string name="user_aspect_ratio_settings_button_description">Change aspect ratio</string> <!-- Description of the camera compat button for applying stretched issues treatment in the hint for compatibility control. [CHAR LIMIT=NONE] --> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 0998e7134e00..54f89846ac85 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -16,12 +16,17 @@ package com.android.wm.shell.compatui; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; + +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.TaskInfo; import android.app.TaskInfo.CameraCompatControlState; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.hardware.display.DisplayManager; +import android.provider.Settings; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -41,7 +46,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -104,6 +108,13 @@ public class CompatUIController implements OnDisplaysChangedListener, private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>(); /** + * The active user aspect ratio settings button layout if there is one (there can be at most + * one active). + */ + @Nullable + private UserAspectRatioSettingsWindowManager mUserAspectRatioSettingsLayout; + + /** * The active Letterbox Education layout if there is one (there can be at most one active). * * <p>An active layout is a layout that is eligible to be shown for the associated task but @@ -121,38 +132,51 @@ public class CompatUIController implements OnDisplaysChangedListener, /** Avoid creating display context frequently for non-default display. */ private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0); + @NonNull private final Context mContext; + @NonNull private final ShellController mShellController; + @NonNull private final DisplayController mDisplayController; + @NonNull private final DisplayInsetsController mDisplayInsetsController; + @NonNull private final DisplayImeController mImeController; + @NonNull private final SyncTransactionQueue mSyncQueue; + @NonNull private final ShellExecutor mMainExecutor; + @NonNull private final Lazy<Transitions> mTransitionsLazy; + @NonNull private final DockStateReader mDockStateReader; + @NonNull private final CompatUIConfiguration mCompatUIConfiguration; // Only show each hint once automatically in the process life. + @NonNull private final CompatUIHintsState mCompatUIHintsState; + @NonNull private final CompatUIShellCommandHandler mCompatUIShellCommandHandler; - private CompatUICallback mCallback; + @Nullable + private CompatUICallback mCompatUICallback; // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't // be shown. private boolean mKeyguardShowing; - public CompatUIController(Context context, - ShellInit shellInit, - ShellController shellController, - DisplayController displayController, - DisplayInsetsController displayInsetsController, - DisplayImeController imeController, - SyncTransactionQueue syncQueue, - ShellExecutor mainExecutor, - Lazy<Transitions> transitionsLazy, - DockStateReader dockStateReader, - CompatUIConfiguration compatUIConfiguration, - CompatUIShellCommandHandler compatUIShellCommandHandler) { + public CompatUIController(@NonNull Context context, + @NonNull ShellInit shellInit, + @NonNull ShellController shellController, + @NonNull DisplayController displayController, + @NonNull DisplayInsetsController displayInsetsController, + @NonNull DisplayImeController imeController, + @NonNull SyncTransactionQueue syncQueue, + @NonNull ShellExecutor mainExecutor, + @NonNull Lazy<Transitions> transitionsLazy, + @NonNull DockStateReader dockStateReader, + @NonNull CompatUIConfiguration compatUIConfiguration, + @NonNull CompatUIShellCommandHandler compatUIShellCommandHandler) { mContext = context; mShellController = shellController; mDisplayController = displayController; @@ -175,9 +199,9 @@ public class CompatUIController implements OnDisplaysChangedListener, mCompatUIShellCommandHandler.onInit(); } - /** Sets the callback for UI interactions. */ - public void setCompatUICallback(CompatUICallback callback) { - mCallback = callback; + /** Sets the callback for Compat UI interactions. */ + public void setCompatUICallback(@NonNull CompatUICallback compatUiCallback) { + mCompatUICallback = compatUiCallback; } /** @@ -187,7 +211,7 @@ public class CompatUIController implements OnDisplaysChangedListener, * @param taskInfo {@link TaskInfo} task the activity is in. * @param taskListener listener to handle the Task Surface placement. */ - public void onCompatInfoChanged(TaskInfo taskInfo, + public void onCompatInfoChanged(@NonNull TaskInfo taskInfo, @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (taskInfo != null && !taskInfo.topActivityInSizeCompat) { mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId); @@ -203,6 +227,16 @@ public class CompatUIController implements OnDisplaysChangedListener, createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + // The user aspect ratio button should not be handled when a new TaskInfo is + // sent because of a double tap or when in multi-window mode. + if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + return; + } + if (!taskInfo.isFromLetterboxDoubleTap) { + createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); + } } } @@ -280,8 +314,8 @@ public class CompatUIController implements OnDisplaysChangedListener, return mDisplaysWithIme.contains(displayId); } - private void createOrUpdateCompatLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateCompatLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId); if (layout != null) { if (layout.needsToBeRecreated(taskInfo, taskListener)) { @@ -314,7 +348,7 @@ public class CompatUIController implements OnDisplaysChangedListener, CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) { return new CompatUIWindowManager(context, - taskInfo, mSyncQueue, mCallback, taskListener, + taskInfo, mSyncQueue, mCompatUICallback, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState, mCompatUIConfiguration, this::onRestartButtonClicked); } @@ -328,12 +362,12 @@ public class CompatUIController implements OnDisplaysChangedListener, mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId); onCompatInfoChanged(taskInfoState.first, taskInfoState.second); } else { - mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); + mCompatUICallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId); } } - private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateLetterboxEduLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveLetterboxEduLayout != null) { if (mActiveLetterboxEduLayout.needsToBeRecreated(taskInfo, taskListener)) { mActiveLetterboxEduLayout.release(); @@ -377,8 +411,8 @@ public class CompatUIController implements OnDisplaysChangedListener, mDockStateReader, mCompatUIConfiguration); } - private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateRestartDialogLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { RestartDialogWindowManager layout = mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId); if (layout != null) { @@ -423,7 +457,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onRestartDialogCallback( Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) { mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId); - mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); + mCompatUICallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId); } private void onRestartDialogDismissCallback( @@ -432,8 +466,8 @@ public class CompatUIController implements OnDisplaysChangedListener, onCompatInfoChanged(stateInfo.first, stateInfo.second); } - private void createOrUpdateReachabilityEduLayout(TaskInfo taskInfo, - ShellTaskOrganizer.TaskListener taskListener) { + private void createOrUpdateReachabilityEduLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { if (mActiveReachabilityEduLayout != null) { if (mActiveReachabilityEduLayout.needsToBeRecreated(taskInfo, taskListener)) { mActiveReachabilityEduLayout.release(); @@ -474,14 +508,67 @@ public class CompatUIController implements OnDisplaysChangedListener, ShellTaskOrganizer.TaskListener taskListener) { return new ReachabilityEduWindowManager(context, taskInfo, mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), - mCompatUIConfiguration, mMainExecutor); + mCompatUIConfiguration, mMainExecutor, this::onInitialReachabilityEduDismissed); } + private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, + @NonNull ShellTaskOrganizer.TaskListener taskListener) { + // We need to update the UI otherwise it will not be shown until the user relaunches the app + createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); + } + + private void createOrUpdateUserAspectRatioSettingsLayout(@NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + if (mUserAspectRatioSettingsLayout != null) { + if (mUserAspectRatioSettingsLayout.needsToBeRecreated(taskInfo, taskListener)) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } else { + // UI already exists, update the UI layout. + if (!mUserAspectRatioSettingsLayout.updateCompatInfo(taskInfo, taskListener, + showOnDisplay(mUserAspectRatioSettingsLayout.getDisplayId()))) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } + return; + } + } + + // Create a new UI layout. + final Context context = getOrCreateDisplayContext(taskInfo.displayId); + if (context == null) { + return; + } + final UserAspectRatioSettingsWindowManager newLayout = + createUserAspectRatioSettingsWindowManager(context, taskInfo, taskListener); + if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) { + // The new layout is eligible to be shown, add it the active layouts. + mUserAspectRatioSettingsLayout = newLayout; + } + } + + @VisibleForTesting + @NonNull + UserAspectRatioSettingsWindowManager createUserAspectRatioSettingsWindowManager( + @NonNull Context context, @NonNull TaskInfo taskInfo, + @Nullable ShellTaskOrganizer.TaskListener taskListener) { + return new UserAspectRatioSettingsWindowManager(context, taskInfo, mSyncQueue, + taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId), + mCompatUIHintsState, this::launchUserAspectRatioSettings, mMainExecutor); + } + + private void launchUserAspectRatioSettings( + @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { + final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivity(intent); + } private void removeLayouts(int taskId) { - final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId); - if (layout != null) { - layout.release(); + final CompatUIWindowManager compatLayout = mActiveCompatLayouts.get(taskId); + if (compatLayout != null) { + compatLayout.release(); mActiveCompatLayouts.remove(taskId); } @@ -502,6 +589,12 @@ public class CompatUIController implements OnDisplaysChangedListener, mActiveReachabilityEduLayout.release(); mActiveReachabilityEduLayout = null; } + + if (mUserAspectRatioSettingsLayout != null + && mUserAspectRatioSettingsLayout.getTaskId() == taskId) { + mUserAspectRatioSettingsLayout.release(); + mUserAspectRatioSettingsLayout = null; + } } private Context getOrCreateDisplayContext(int displayId) { @@ -557,6 +650,10 @@ public class CompatUIController implements OnDisplaysChangedListener, if (mActiveReachabilityEduLayout != null && condition.test(mActiveReachabilityEduLayout)) { callback.accept(mActiveReachabilityEduLayout); } + if (mUserAspectRatioSettingsLayout != null && condition.test( + mUserAspectRatioSettingsLayout)) { + callback.accept(mUserAspectRatioSettingsLayout); + } } /** An implementation of {@link OnInsetsChangedListener} for a given display id. */ @@ -591,4 +688,14 @@ public class CompatUIController implements OnDisplaysChangedListener, insetsChanged(insetsState); } } + + /** + * A class holding the state of the compat UI hints, which is shared between all compat UI + * window managers. + */ + static class CompatUIHintsState { + boolean mHasShownSizeCompatHint; + boolean mHasShownCameraCompatHint; + boolean mHasShownUserAspectRatioSettingsButtonHint; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index 065806df3dc8..ce3c5093fdd4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -38,6 +38,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import java.util.function.Consumer; @@ -235,15 +236,4 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED; } - - /** - * A class holding the state of the compat UI hints, which is shared between all compat UI - * window managers. - */ - static class CompatUIHintsState { - @VisibleForTesting - boolean mHasShownSizeCompatHint; - @VisibleForTesting - boolean mHasShownCameraCompatHint; - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java index 95bb1fe1c986..9de3f9dec34e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java @@ -36,6 +36,8 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; +import java.util.function.BiConsumer; + /** * Window manager for the reachability education */ @@ -73,6 +75,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { // we need to animate them. private boolean mHasLetterboxSizeChanged; + private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback; + @Nullable @VisibleForTesting ReachabilityEduLayout mLayout; @@ -80,7 +84,8 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { ReachabilityEduWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, - CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor) { + CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor, + BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback) { super(context, taskInfo, syncQueue, taskListener, displayLayout); mIsActivityLetterboxed = taskInfo.isLetterboxDoubleTapEnabled; mLetterboxVerticalPosition = taskInfo.topActivityLetterboxVerticalPosition; @@ -89,6 +94,7 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { mTopActivityLetterboxHeight = taskInfo.topActivityLetterboxHeight; mCompatUIConfiguration = compatUIConfiguration; mMainExecutor = mainExecutor; + mOnDismissCallback = onDismissCallback; } @Override @@ -217,13 +223,17 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { return; } final TaskInfo lastTaskInfo = getLastTaskInfo(); + final boolean hasSeenHorizontalReachabilityEdu = + mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo); + final boolean hasSeenVerticalReachabilityEdu = + mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo); final boolean eligibleForDisplayHorizontalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo) + || !hasSeenHorizontalReachabilityEdu || (mHasUserDoubleTapped && (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); final boolean eligibleForDisplayVerticalEducation = mForceUpdate - || !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo) + || !hasSeenVerticalReachabilityEdu || (mHasUserDoubleTapped && (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION || mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION)); @@ -239,6 +249,14 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { if (!mHasLetterboxSizeChanged) { updateHideTime(); mMainExecutor.executeDelayed(this::hideReachability, DISAPPEAR_DELAY_MS); + // If reachability education has been seen for the first time, trigger callback to + // display aspect ratio settings button once reachability education disappears + if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu) + || hasShownVerticalReachabilityEduFirstTime( + hasSeenVerticalReachabilityEdu)) { + mMainExecutor.executeDelayed(this::triggerOnDismissCallback, + DISAPPEAR_DELAY_MS); + } } mHasUserDoubleTapped = false; } else { @@ -246,6 +264,38 @@ class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract { } } + /** + * Compares the value of + * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the + * layout is shown. Horizontal reachability education is considered seen for the first time if + * prior to viewing the layout, + * {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false} + * but becomes {@code true} once the current layout is shown. + */ + private boolean hasShownHorizontalReachabilityEduFirstTime( + boolean previouslyShownHorizontalReachabilityEducation) { + return !previouslyShownHorizontalReachabilityEducation + && mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo()); + } + + /** + * Compares the value of + * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the + * layout is shown. Horizontal reachability education is considered seen for the first time if + * prior to viewing the layout, + * {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false} + * but becomes {@code true} once the current layout is shown. + */ + private boolean hasShownVerticalReachabilityEduFirstTime( + boolean previouslyShownVerticalReachabilityEducation) { + return !previouslyShownVerticalReachabilityEducation + && mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo()); + } + + private void triggerOnDismissCallback() { + mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener()); + } + private void hideReachability() { if (mLayout == null || !shouldHideEducation()) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java new file mode 100644 index 000000000000..5eeb3b650074 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayout.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 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.compatui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.annotation.IdRes; +import android.annotation.NonNull; +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.wm.shell.R; + +/** + * Layout for the user aspect ratio button which opens the app list page in settings + * and allows users to change apps aspect ratio. + */ +public class UserAspectRatioSettingsLayout extends LinearLayout { + + private static final float ALPHA_FULL_TRANSPARENT = 0f; + + private static final float ALPHA_FULL_OPAQUE = 1f; + + private static final long VISIBILITY_ANIMATION_DURATION_MS = 50; + + private static final String ALPHA_PROPERTY_NAME = "alpha"; + + private UserAspectRatioSettingsWindowManager mWindowManager; + + public UserAspectRatioSettingsLayout(Context context) { + this(context, null); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public UserAspectRatioSettingsLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + void inject(@NonNull UserAspectRatioSettingsWindowManager windowManager) { + mWindowManager = windowManager; + } + + void setUserAspectRatioSettingsHintVisibility(boolean show) { + setViewVisibility(R.id.user_aspect_ratio_settings_hint, show); + } + + void setUserAspectRatioButtonVisibility(boolean show) { + setViewVisibility(R.id.user_aspect_ratio_settings_button, show); + // Hint should never be visible without button. + if (!show) { + setUserAspectRatioSettingsHintVisibility(/* show= */ false); + } + } + + private void setViewVisibility(@IdRes int resId, boolean show) { + final View view = findViewById(resId); + int visibility = show ? View.VISIBLE : View.GONE; + if (view.getVisibility() == visibility) { + return; + } + if (show) { + showItem(view); + } else { + view.setVisibility(visibility); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + // Need to relayout after changes like hiding / showing a hint since they affect size. + // Doing this directly in setUserAspectRatioButtonVisibility can result in flaky animation. + mWindowManager.relayout(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + + final ImageButton userAspectRatioButton = + findViewById(R.id.user_aspect_ratio_settings_button); + userAspectRatioButton.setOnClickListener( + view -> mWindowManager.onUserAspectRatioSettingsButtonClicked()); + userAspectRatioButton.setOnLongClickListener(view -> { + mWindowManager.onUserAspectRatioSettingsButtonLongClicked(); + return true; + }); + + final LinearLayout sizeCompatHint = findViewById(R.id.user_aspect_ratio_settings_hint); + ((TextView) sizeCompatHint.findViewById(R.id.compat_mode_hint_text)) + .setText(R.string.user_aspect_ratio_settings_button_hint); + sizeCompatHint.setOnClickListener( + view -> setUserAspectRatioSettingsHintVisibility(/* show= */ false)); + } + + private void showItem(@NonNull View view) { + view.setVisibility(View.VISIBLE); + final ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, + ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); + fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS); + fadeIn.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.VISIBLE); + } + }); + fadeIn.start(); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java new file mode 100644 index 000000000000..bd53dc7390c8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2023 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.compatui; + +import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Rect; +import android.os.SystemClock; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; + +import java.util.function.BiConsumer; + +/** + * Window manager for the user aspect ratio settings button which allows users to go to + * app settings and change apps aspect ratio. + */ +class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract { + + private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L; + + private static final long HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 4000L; + + private long mNextButtonHideTimeMs = -1L; + + private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked; + + private final ShellExecutor mShellExecutor; + + @VisibleForTesting + @NonNull + final CompatUIHintsState mCompatUIHintsState; + + @Nullable + private UserAspectRatioSettingsLayout mLayout; + + // Remember the last reported states in case visibility changes due to keyguard or IME updates. + @VisibleForTesting + boolean mHasUserAspectRatioSettingsButton; + + UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo, + @NonNull SyncTransactionQueue syncQueue, + @Nullable ShellTaskOrganizer.TaskListener taskListener, + @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState, + @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked, + @NonNull ShellExecutor shellExecutor) { + super(context, taskInfo, syncQueue, taskListener, displayLayout); + mShellExecutor = shellExecutor; + mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); + mCompatUIHintsState = compatUIHintsState; + mOnButtonClicked = onButtonClicked; + } + + @Override + protected int getZOrder() { + return TASK_CHILD_LAYER_COMPAT_UI + 1; + } + + @Override + protected @Nullable View getLayout() { + return mLayout; + } + + @Override + protected void removeLayout() { + mLayout = null; + } + + @Override + protected boolean eligibleToShowLayout() { + return mHasUserAspectRatioSettingsButton; + } + + @Override + protected View createLayout() { + mLayout = inflateLayout(); + mLayout.inject(this); + + updateVisibilityOfViews(); + + return mLayout; + } + + @VisibleForTesting + UserAspectRatioSettingsLayout inflateLayout() { + return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( + R.layout.user_aspect_ratio_settings_layout, null); + } + + @Override + public boolean updateCompatInfo(@NonNull TaskInfo taskInfo, + @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) { + final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton; + mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo); + + if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) { + return false; + } + + if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) { + updateVisibilityOfViews(); + } + return true; + } + + /** Called when the user aspect ratio settings button is clicked. */ + void onUserAspectRatioSettingsButtonClicked() { + mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener()); + } + + /** Called when the user aspect ratio settings button is long clicked. */ + void onUserAspectRatioSettingsButtonLongClicked() { + if (mLayout == null) { + return; + } + mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); + mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, + HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + } + + @Override + @VisibleForTesting + public void updateSurfacePosition() { + if (mLayout == null) { + return; + } + // Position of the button in the container coordinate. + final Rect taskBounds = getTaskBounds(); + final Rect taskStableBounds = getTaskStableBounds(); + final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? taskStableBounds.left - taskBounds.left + : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); + final int positionY = taskStableBounds.bottom - taskBounds.top + - mLayout.getMeasuredHeight(); + updateSurfacePosition(positionX, positionY); + } + + @VisibleForTesting + void updateVisibilityOfViews() { + if (mHasUserAspectRatioSettingsButton) { + mShellExecutor.executeDelayed(this::showUserAspectRatioButton, + SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + mNextButtonHideTimeMs = updateHideTime(HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, + HIDE_USER_ASPECT_RATIO_BUTTON_DELAY_MS); + } else { + mShellExecutor.removeCallbacks(this::showUserAspectRatioButton); + mShellExecutor.execute(this::hideUserAspectRatioButton); + } + } + + private void showUserAspectRatioButton() { + if (mLayout == null) { + return; + } + mLayout.setUserAspectRatioButtonVisibility(true); + // Only show by default for the first time. + if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) { + mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true); + mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + } + } + + private void hideUserAspectRatioButton() { + if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) { + return; + } + mLayout.setUserAspectRatioButtonVisibility(false); + } + + private boolean isHideDelayReached(long nextHideTime) { + return SystemClock.uptimeMillis() >= nextHideTime; + } + + private long updateHideTime(long hideDelay) { + return SystemClock.uptimeMillis() + hideDelay; + } + + private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) { + return taskInfo.topActivityEligibleForUserAspectRatioButton + && (taskInfo.topActivityBoundsLetterboxed + || taskInfo.isUserFullscreenOverrideEnabled); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index a6501f05475f..efc69ebd395c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -58,6 +58,8 @@ import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import dagger.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,8 +68,6 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import dagger.Lazy; - /** * Tests for {@link CompatUIController}. * @@ -82,21 +82,36 @@ public class CompatUIControllerTest extends ShellTestCase { private CompatUIController mController; private ShellInit mShellInit; - private @Mock ShellController mMockShellController; - private @Mock DisplayController mMockDisplayController; - private @Mock DisplayInsetsController mMockDisplayInsetsController; - private @Mock DisplayLayout mMockDisplayLayout; - private @Mock DisplayImeController mMockImeController; - private @Mock ShellTaskOrganizer.TaskListener mMockTaskListener; - private @Mock SyncTransactionQueue mMockSyncQueue; - private @Mock ShellExecutor mMockExecutor; - private @Mock Lazy<Transitions> mMockTransitionsLazy; - private @Mock CompatUIWindowManager mMockCompatLayout; - private @Mock LetterboxEduWindowManager mMockLetterboxEduLayout; - private @Mock RestartDialogWindowManager mMockRestartDialogLayout; - private @Mock DockStateReader mDockStateReader; - private @Mock CompatUIConfiguration mCompatUIConfiguration; - private @Mock CompatUIShellCommandHandler mCompatUIShellCommandHandler; + @Mock + private ShellController mMockShellController; + @Mock + private DisplayController mMockDisplayController; + @Mock + private DisplayInsetsController mMockDisplayInsetsController; + @Mock + private DisplayLayout mMockDisplayLayout; + @Mock + private DisplayImeController mMockImeController; + @Mock + private ShellTaskOrganizer.TaskListener mMockTaskListener; + @Mock + private SyncTransactionQueue mMockSyncQueue; + @Mock + private ShellExecutor mMockExecutor; + @Mock + private Lazy<Transitions> mMockTransitionsLazy; + @Mock + private CompatUIWindowManager mMockCompatLayout; + @Mock + private LetterboxEduWindowManager mMockLetterboxEduLayout; + @Mock + private RestartDialogWindowManager mMockRestartDialogLayout; + @Mock + private DockStateReader mDockStateReader; + @Mock + private CompatUIConfiguration mCompatUIConfiguration; + @Mock + private CompatUIShellCommandHandler mCompatUIShellCommandHandler; @Captor ArgumentCaptor<OnInsetsChangedListener> mOnInsetsChangedListenerCaptor; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java index 5f294d53b662..3bce2b824e28 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUILayoutTest.java @@ -44,7 +44,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 78c3cbdaace6..4c837e635939 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -53,7 +53,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; import junit.framework.Assert; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java index 973a99c269ea..a802f15a0a41 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/ReachabilityEduWindowManagerTest.java @@ -40,6 +40,8 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.function.BiConsumer; + /** * Tests for {@link ReachabilityEduWindowManager}. * @@ -57,6 +59,8 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private CompatUIConfiguration mCompatUIConfiguration; @Mock private DisplayLayout mDisplayLayout; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback; private TestShellExecutor mExecutor; private TaskInfo mTaskInfo; private ReachabilityEduWindowManager mWindowManager; @@ -104,6 +108,7 @@ public class ReachabilityEduWindowManagerTest extends ShellTestCase { private ReachabilityEduWindowManager createReachabilityEduWindowManager(TaskInfo taskInfo) { return new ReachabilityEduWindowManager(mContext, taskInfo, mSyncTransactionQueue, - mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor); + mTaskListener, mDisplayLayout, mCompatUIConfiguration, mExecutor, + mOnDismissCallback); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java new file mode 100644 index 000000000000..1fee153877b5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsLayoutTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 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.compatui; + +import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.app.TaskInfo.CameraCompatControlState; +import android.content.ComponentName; +import android.testing.AndroidTestingRunner; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.SurfaceControlViewHost; +import android.widget.ImageButton; +import android.widget.LinearLayout; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.function.BiConsumer; + +/** + * Tests for {@link UserAspectRatioSettingsLayout}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UserAspectRatioSettingsLayoutTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class UserAspectRatioSettingsLayoutTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock + private SyncTransactionQueue mSyncTransactionQueue; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> + mOnUserAspectRatioSettingsButtonClicked; + @Mock + private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock + private SurfaceControlViewHost mViewHost; + @Captor + private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor; + @Captor + private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor; + + private UserAspectRatioSettingsWindowManager mWindowManager; + private UserAspectRatioSettingsLayout mLayout; + private TaskInfo mTaskInfo; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, + mSyncTransactionQueue, mTaskListener, new DisplayLayout(), + new CompatUIController.CompatUIHintsState(), + mOnUserAspectRatioSettingsButtonClicked, new TestShellExecutor()); + + mLayout = (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate( + R.layout.user_aspect_ratio_settings_layout, null); + mLayout.inject(mWindowManager); + + spyOn(mWindowManager); + spyOn(mLayout); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + doReturn(mLayout).when(mWindowManager).inflateLayout(); + } + + @Test + public void testOnClickForUserAspectRatioSettingsButton() { + final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button); + button.performClick(); + + verify(mWindowManager).onUserAspectRatioSettingsButtonClicked(); + verify(mOnUserAspectRatioSettingsButtonClicked).accept( + mUserAspectRationTaskInfoCaptor.capture(), + mUserAspectRatioTaskListenerCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = + new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(), + mUserAspectRatioTaskListenerCaptor.getValue()); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); + } + + @Test + public void testOnLongClickForUserAspectRatioButton() { + doNothing().when(mWindowManager).onUserAspectRatioSettingsButtonLongClicked(); + + final ImageButton button = mLayout.findViewById(R.id.user_aspect_ratio_settings_button); + button.performLongClick(); + + verify(mWindowManager).onUserAspectRatioSettingsButtonLongClicked(); + } + + @Test + public void testOnClickForUserAspectRatioSettingsHint() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + final LinearLayout sizeCompatHint = mLayout.findViewById( + R.id.user_aspect_ratio_settings_hint); + sizeCompatHint.performClick(); + + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ false); + } + + private static TaskInfo createTaskInfo(boolean hasSizeCompat, + @CameraCompatControlState int cameraCompatControlState) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = TASK_ID; + taskInfo.topActivityInSizeCompat = hasSizeCompat; + taskInfo.cameraCompatControlState = cameraCompatControlState; + taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + return taskInfo; + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java new file mode 100644 index 000000000000..b48538ca99ca --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2023 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.compatui; + +import static android.view.WindowInsets.Type.navigationBars; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.ComponentName; +import android.content.res.Configuration; +import android.graphics.Rect; +import android.testing.AndroidTestingRunner; +import android.util.Pair; +import android.view.DisplayInfo; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.BiConsumer; + +/** + * Tests for {@link UserAspectRatioSettingsWindowManager}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UserAspectRatioSettingsWindowManagerTest + */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class UserAspectRatioSettingsWindowManagerTest extends ShellTestCase { + + private static final int TASK_ID = 1; + + @Mock private SyncTransactionQueue mSyncTransactionQueue; + @Mock + private BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> + mOnUserAspectRatioSettingsButtonClicked; + @Mock private ShellTaskOrganizer.TaskListener mTaskListener; + @Mock private UserAspectRatioSettingsLayout mLayout; + @Mock private SurfaceControlViewHost mViewHost; + @Captor + private ArgumentCaptor<ShellTaskOrganizer.TaskListener> mUserAspectRatioTaskListenerCaptor; + @Captor + private ArgumentCaptor<TaskInfo> mUserAspectRationTaskInfoCaptor; + + private final Set<String> mPackageNameCache = new HashSet<>(); + + private UserAspectRatioSettingsWindowManager mWindowManager; + private TaskInfo mTaskInfo; + + private TestShellExecutor mExecutor; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mExecutor = new TestShellExecutor(); + mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo, + mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mOnUserAspectRatioSettingsButtonClicked, mExecutor); + spyOn(mWindowManager); + doReturn(mLayout).when(mWindowManager).inflateLayout(); + doReturn(mViewHost).when(mWindowManager).createSurfaceViewHost(); + } + + @Test + public void testCreateUserAspectRatioButton() { + // Doesn't create layout if show is false. + mWindowManager.mHasUserAspectRatioSettingsButton = true; + assertTrue(mWindowManager.createLayout(/* canShow= */ false)); + + verify(mWindowManager, never()).inflateLayout(); + + // Doesn't create hint popup. + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + assertTrue(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager).inflateLayout(); + mExecutor.flushAll(); + verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true); + verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + + // Creates hint popup. + clearInvocations(mWindowManager); + clearInvocations(mLayout); + mWindowManager.release(); + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = false; + assertTrue(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager).inflateLayout(); + assertNotNull(mLayout); + mExecutor.flushAll(); + verify(mLayout).setUserAspectRatioButtonVisibility(/* show= */ true); + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + assertTrue(mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint); + + // Returns false and doesn't create layout if mHasUserAspectRatioSettingsButton is false. + clearInvocations(mWindowManager); + mWindowManager.release(); + mWindowManager.mHasUserAspectRatioSettingsButton = false; + assertFalse(mWindowManager.createLayout(/* canShow= */ true)); + + verify(mWindowManager, never()).inflateLayout(); + } + + @Test + public void testRelease() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + + mWindowManager.release(); + + verify(mViewHost).release(); + } + + @Test + public void testUpdateCompatInfo() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ true); + + // No diff + clearInvocations(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true)); + + verify(mWindowManager, never()).updateSurfacePosition(); + verify(mWindowManager, never()).release(); + verify(mWindowManager, never()).createLayout(anyBoolean()); + + + // Change task listener, recreate button. + clearInvocations(mWindowManager); + final ShellTaskOrganizer.TaskListener newTaskListener = mock( + ShellTaskOrganizer.TaskListener.class); + assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + + verify(mWindowManager).release(); + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Change has eligibleForUserAspectRatioButton to false, dispose the component + clearInvocations(mWindowManager); + clearInvocations(mLayout); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + assertFalse( + mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true)); + verify(mWindowManager).release(); + } + + @Test + public void testUpdateCompatInfoLayoutNotInflatedYet() { + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.createLayout(/* canShow= */ false); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to false and pass canShow true, layout shouldn't be + // inflated + clearInvocations(mWindowManager); + TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + false, /* topActivityBoundsLetterboxed */ true); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager, never()).inflateLayout(); + + // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated. + clearInvocations(mWindowManager); + taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */ + true, /* topActivityBoundsLetterboxed */ true); + mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + } + + @Test + public void testUpdateDisplayLayout() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout1 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout1); + verify(mWindowManager).updateSurfacePosition(); + + // No update if the display bounds is the same. + clearInvocations(mWindowManager); + final DisplayLayout displayLayout2 = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ false, /* hasStatusBar= */ false); + mWindowManager.updateDisplayLayout(displayLayout2); + verify(mWindowManager, never()).updateSurfacePosition(); + } + + @Test + public void testUpdateDisplayLayoutInsets() { + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = 1000; + displayInfo.logicalHeight = 2000; + final DisplayLayout displayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + + // Update if the insets change on the existing display layout + clearInvocations(mWindowManager); + InsetsState insetsState = new InsetsState(); + insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000)); + InsetsSource insetsSource = new InsetsSource( + InsetsSource.createId(null, 0, navigationBars()), navigationBars()); + insetsSource.setFrame(0, 1800, 1000, 2000); + insetsState.addSource(insetsSource); + displayLayout.setInsets(mContext.getResources(), insetsState); + mWindowManager.updateDisplayLayout(displayLayout); + verify(mWindowManager).updateSurfacePosition(); + } + + @Test + public void testUpdateVisibility() { + // Create button if it is not created. + mWindowManager.removeLayout(); + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.updateVisibility(/* canShow= */ true); + + verify(mWindowManager).createLayout(/* canShow= */ true); + + // Hide button. + clearInvocations(mWindowManager); + doReturn(View.VISIBLE).when(mLayout).getVisibility(); + mWindowManager.updateVisibility(/* canShow= */ false); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mLayout).setVisibility(View.GONE); + + // Show button. + doReturn(View.GONE).when(mLayout).getVisibility(); + mWindowManager.updateVisibility(/* canShow= */ true); + + verify(mWindowManager, never()).createLayout(anyBoolean()); + verify(mLayout).setVisibility(View.VISIBLE); + } + + @Test + public void testAttachToParentSurface() { + final SurfaceControl.Builder b = new SurfaceControl.Builder(); + mWindowManager.attachToParentSurface(b); + + verify(mTaskListener).attachChildSurfaceToTask(TASK_ID, b); + } + + @Test + public void testOnUserAspectRatioButtonClicked() { + mWindowManager.onUserAspectRatioSettingsButtonClicked(); + + verify(mOnUserAspectRatioSettingsButtonClicked).accept( + mUserAspectRationTaskInfoCaptor.capture(), + mUserAspectRatioTaskListenerCaptor.capture()); + final Pair<TaskInfo, ShellTaskOrganizer.TaskListener> result = + new Pair<>(mUserAspectRationTaskInfoCaptor.getValue(), + mUserAspectRatioTaskListenerCaptor.getValue()); + Assert.assertEquals(mTaskInfo, result.first); + Assert.assertEquals(mTaskListener, result.second); + } + + @Test + public void testOnUserAspectRatioButtonLongClicked_showHint() { + // Not create hint popup. + mWindowManager.mHasUserAspectRatioSettingsButton = true; + mWindowManager.mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true; + mWindowManager.createLayout(/* canShow= */ true); + + verify(mWindowManager).inflateLayout(); + verify(mLayout, never()).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + + mWindowManager.onUserAspectRatioSettingsButtonLongClicked(); + + verify(mLayout).setUserAspectRatioSettingsHintVisibility(/* show= */ true); + } + + @Test + public void testWhenDockedStateHasChanged_needsToBeRecreated() { + ActivityManager.RunningTaskInfo newTaskInfo = new ActivityManager.RunningTaskInfo(); + newTaskInfo.configuration.uiMode |= Configuration.UI_MODE_TYPE_DESK; + + Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener)); + } + + private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton, + boolean topActivityBoundsLetterboxed) { + ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo(); + taskInfo.taskId = TASK_ID; + taskInfo.topActivityEligibleForUserAspectRatioButton = eligibleForUserAspectRatioButton; + taskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed; + taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; + taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity"); + return taskInfo; + } +} diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index ba242ecd0ec3..01786becda61 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -969,8 +969,10 @@ final class LetterboxUiController { final Rect innerFrame = hasInheritedLetterboxBehavior() ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); - // We need to notify Shell that letterbox position has changed. - mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); + if (mDoubleTapEvent) { + // We need to notify Shell that letterbox position has changed. + mActivityRecord.getTask().dispatchTaskInfoChangedIfNeeded(true /* force */); + } } else if (mLetterbox != null) { mLetterbox.hide(); } @@ -1242,6 +1244,7 @@ final class LetterboxUiController { ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER; logLetterboxPositionChange(changeToLog); + mDoubleTapEvent = true; } else if (mLetterbox.getInnerFrame().right < x) { // Moving to the next stop on the right side of the app window: left > center > right. mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop( @@ -1252,8 +1255,8 @@ final class LetterboxUiController { ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER; logLetterboxPositionChange(changeToLog); + mDoubleTapEvent = true; } - mDoubleTapEvent = true; // TODO(197549949): Add animation for transition. mActivityRecord.recomputeConfiguration(); } @@ -1281,6 +1284,7 @@ final class LetterboxUiController { ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; logLetterboxPositionChange(changeToLog); + mDoubleTapEvent = true; } else if (mLetterbox.getInnerFrame().bottom < y) { // Moving to the next stop on the bottom side of the app window: top > center > bottom. mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop( @@ -1291,8 +1295,8 @@ final class LetterboxUiController { ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER; logLetterboxPositionChange(changeToLog); + mDoubleTapEvent = true; } - mDoubleTapEvent = true; // TODO(197549949): Add animation for transition. mActivityRecord.recomputeConfiguration(); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8fbaac210283..0f0189e614b6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3459,6 +3459,8 @@ class Task extends TaskFragment { info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET; + info.isUserFullscreenOverrideEnabled = top != null + && top.mLetterboxUiController.shouldApplyUserFullscreenOverride(); info.isFromLetterboxDoubleTap = top != null && top.mLetterboxUiController.isFromDoubleTap(); if (info.isLetterboxDoubleTapEnabled) { info.topActivityLetterboxWidth = top.getBounds().width(); |