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