1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.compatui;
18 
19 import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.app.TaskInfo;
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.os.SystemClock;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.accessibility.AccessibilityManager;
30 
31 import com.android.internal.annotations.VisibleForTesting;
32 import com.android.wm.shell.R;
33 import com.android.wm.shell.ShellTaskOrganizer;
34 import com.android.wm.shell.common.DisplayLayout;
35 import com.android.wm.shell.common.ShellExecutor;
36 import com.android.wm.shell.common.SyncTransactionQueue;
37 import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
38 
39 import java.util.function.BiConsumer;
40 import java.util.function.Consumer;
41 import java.util.function.Function;
42 import java.util.function.Supplier;
43 
44 /**
45  * Window manager for the user aspect ratio settings button which allows users to go to
46  * app settings and change apps aspect ratio.
47  */
48 class UserAspectRatioSettingsWindowManager extends CompatUIWindowManagerAbstract {
49 
50     private static final long SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS = 500L;
51 
52     private long mNextButtonHideTimeMs = -1L;
53 
54     private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnButtonClicked;
55 
56     private final Function<Integer, Integer> mDisappearTimeSupplier;
57 
58     private final ShellExecutor mShellExecutor;
59 
60     @NonNull
61     private final Supplier<Boolean> mUserAspectRatioButtonShownChecker;
62 
63     @NonNull
64     private final Consumer<Boolean> mUserAspectRatioButtonStateConsumer;
65 
66     @VisibleForTesting
67     @NonNull
68     final CompatUIHintsState mCompatUIHintsState;
69 
70     @Nullable
71     private UserAspectRatioSettingsLayout mLayout;
72 
73     // Remember the last reported states in case visibility changes due to keyguard or IME updates.
74     @VisibleForTesting
75     boolean mHasUserAspectRatioSettingsButton;
76 
UserAspectRatioSettingsWindowManager(@onNull 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, @NonNull Function<Integer, Integer> disappearTimeSupplier, @NonNull Supplier<Boolean> userAspectRatioButtonStateChecker, @NonNull Consumer<Boolean> userAspectRatioButtonShownConsumer)77     UserAspectRatioSettingsWindowManager(@NonNull Context context, @NonNull TaskInfo taskInfo,
78             @NonNull SyncTransactionQueue syncQueue,
79             @Nullable ShellTaskOrganizer.TaskListener taskListener,
80             @NonNull DisplayLayout displayLayout, @NonNull CompatUIHintsState compatUIHintsState,
81             @NonNull BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onButtonClicked,
82             @NonNull ShellExecutor shellExecutor,
83             @NonNull Function<Integer, Integer> disappearTimeSupplier,
84             @NonNull Supplier<Boolean> userAspectRatioButtonStateChecker,
85             @NonNull Consumer<Boolean> userAspectRatioButtonShownConsumer) {
86         super(context, taskInfo, syncQueue, taskListener, displayLayout);
87         mShellExecutor = shellExecutor;
88         mUserAspectRatioButtonShownChecker = userAspectRatioButtonStateChecker;
89         mUserAspectRatioButtonStateConsumer = userAspectRatioButtonShownConsumer;
90         mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
91         mCompatUIHintsState = compatUIHintsState;
92         mOnButtonClicked = onButtonClicked;
93         mDisappearTimeSupplier = disappearTimeSupplier;
94     }
95 
96     @Override
getZOrder()97     protected int getZOrder() {
98         return TASK_CHILD_LAYER_COMPAT_UI + 1;
99     }
100 
101     @Override
getLayout()102     protected @Nullable View getLayout() {
103         return mLayout;
104     }
105 
106     @Override
removeLayout()107     protected void removeLayout() {
108         mLayout = null;
109     }
110 
111     @Override
eligibleToShowLayout()112     protected boolean eligibleToShowLayout() {
113         return mHasUserAspectRatioSettingsButton;
114     }
115 
116     @Override
createLayout()117     protected View createLayout() {
118         mLayout = inflateLayout();
119         mLayout.inject(this);
120 
121         updateVisibilityOfViews();
122 
123         return mLayout;
124     }
125 
126     @VisibleForTesting
inflateLayout()127     UserAspectRatioSettingsLayout inflateLayout() {
128         return (UserAspectRatioSettingsLayout) LayoutInflater.from(mContext).inflate(
129                 R.layout.user_aspect_ratio_settings_layout, null);
130     }
131 
132     @Override
updateCompatInfo(@onNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow)133     public boolean updateCompatInfo(@NonNull TaskInfo taskInfo,
134             @NonNull ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
135         final boolean prevHasUserAspectRatioSettingsButton = mHasUserAspectRatioSettingsButton;
136         mHasUserAspectRatioSettingsButton = getHasUserAspectRatioSettingsButton(taskInfo);
137 
138         if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
139             return false;
140         }
141 
142         if (prevHasUserAspectRatioSettingsButton != mHasUserAspectRatioSettingsButton) {
143             updateVisibilityOfViews();
144         }
145         return true;
146     }
147 
148     /** Called when the user aspect ratio settings button is clicked. */
onUserAspectRatioSettingsButtonClicked()149     void onUserAspectRatioSettingsButtonClicked() {
150         mOnButtonClicked.accept(getLastTaskInfo(), getTaskListener());
151     }
152 
153     /** Called when the user aspect ratio settings button is long clicked. */
onUserAspectRatioSettingsButtonLongClicked()154     void onUserAspectRatioSettingsButtonLongClicked() {
155         if (mLayout == null) {
156             return;
157         }
158         mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
159         final long disappearTimeMs = getDisappearTimeMs();
160         mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
161         mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
162     }
163 
164     @Override
165     @VisibleForTesting
updateSurfacePosition()166     public void updateSurfacePosition() {
167         if (mLayout == null) {
168             return;
169         }
170         // Position of the button in the container coordinate.
171         final Rect taskBounds = getTaskBounds();
172         final Rect taskStableBounds = getTaskStableBounds();
173         final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
174                 ? taskStableBounds.left - taskBounds.left
175                 : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth();
176         final int positionY = taskStableBounds.bottom - taskBounds.top
177                 - mLayout.getMeasuredHeight();
178         updateSurfacePosition(positionX, positionY);
179     }
180 
181     @VisibleForTesting
updateVisibilityOfViews()182     void updateVisibilityOfViews() {
183         if (mHasUserAspectRatioSettingsButton) {
184             mShellExecutor.executeDelayed(this::showUserAspectRatioButton,
185                     SHOW_USER_ASPECT_RATIO_BUTTON_DELAY_MS);
186             final long disappearTimeMs = getDisappearTimeMs();
187             mNextButtonHideTimeMs = updateHideTime(disappearTimeMs);
188             mShellExecutor.executeDelayed(this::hideUserAspectRatioButton, disappearTimeMs);
189         } else {
190             mShellExecutor.removeCallbacks(this::showUserAspectRatioButton);
191             mShellExecutor.execute(this::hideUserAspectRatioButton);
192         }
193     }
194 
195     @VisibleForTesting
isShowingButton()196     boolean isShowingButton() {
197         return (mUserAspectRatioButtonShownChecker.get()
198                 && !isHideDelayReached(mNextButtonHideTimeMs));
199     }
200 
showUserAspectRatioButton()201     private void showUserAspectRatioButton() {
202         if (mLayout == null) {
203             return;
204         }
205         mLayout.setUserAspectRatioButtonVisibility(true);
206         mUserAspectRatioButtonStateConsumer.accept(true);
207         // Only show by default for the first time.
208         if (!mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint) {
209             mLayout.setUserAspectRatioSettingsHintVisibility(/* show= */ true);
210             mCompatUIHintsState.mHasShownUserAspectRatioSettingsButtonHint = true;
211         }
212     }
213 
hideUserAspectRatioButton()214     private void hideUserAspectRatioButton() {
215         if (mLayout == null || !isHideDelayReached(mNextButtonHideTimeMs)) {
216             return;
217         }
218         mLayout.setUserAspectRatioButtonVisibility(false);
219     }
220 
isHideDelayReached(long nextHideTime)221     private boolean isHideDelayReached(long nextHideTime) {
222         return SystemClock.uptimeMillis() >= nextHideTime;
223     }
224 
updateHideTime(long hideDelay)225     private long updateHideTime(long hideDelay) {
226         return SystemClock.uptimeMillis() + hideDelay;
227     }
228 
getHasUserAspectRatioSettingsButton(@onNull TaskInfo taskInfo)229     private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
230         return taskInfo.topActivityEligibleForUserAspectRatioButton
231                 && (taskInfo.topActivityBoundsLetterboxed
232                     || taskInfo.isUserFullscreenOverrideEnabled)
233                 && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
234     }
235 
getDisappearTimeMs()236     private long getDisappearTimeMs() {
237         return mDisappearTimeSupplier.apply(AccessibilityManager.FLAG_CONTENT_CONTROLS);
238     }
239 }
240