1 /* 2 * Copyright (C) 2021 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.app.TaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; 20 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; 21 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; 22 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.TaskInfo; 27 import android.app.TaskInfo.CameraCompatControlState; 28 import android.content.Context; 29 import android.graphics.Rect; 30 import android.util.Log; 31 import android.util.Pair; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.wm.shell.R; 37 import com.android.wm.shell.ShellTaskOrganizer; 38 import com.android.wm.shell.common.DisplayLayout; 39 import com.android.wm.shell.common.SyncTransactionQueue; 40 import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; 41 42 import java.util.function.Consumer; 43 44 /** 45 * Window manager for the Size Compat restart button and Camera Compat control. 46 */ 47 class CompatUIWindowManager extends CompatUIWindowManagerAbstract { 48 49 /** 50 * The Compat UI should be below the Letterbox Education. 51 */ 52 private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1; 53 54 private final CompatUICallback mCallback; 55 56 private final CompatUIConfiguration mCompatUIConfiguration; 57 58 private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked; 59 60 @NonNull 61 private TaskInfo mTaskInfo; 62 63 // Remember the last reported states in case visibility changes due to keyguard or IME updates. 64 @VisibleForTesting 65 boolean mHasSizeCompat; 66 67 @VisibleForTesting 68 @CameraCompatControlState 69 int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN; 70 71 @VisibleForTesting 72 CompatUIHintsState mCompatUIHintsState; 73 74 @Nullable 75 @VisibleForTesting 76 CompatUILayout mLayout; 77 CompatUIWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, CompatUICallback callback, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked)78 CompatUIWindowManager(Context context, TaskInfo taskInfo, 79 SyncTransactionQueue syncQueue, CompatUICallback callback, 80 ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, 81 CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration, 82 Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) { 83 super(context, taskInfo, syncQueue, taskListener, displayLayout); 84 mTaskInfo = taskInfo; 85 mCallback = callback; 86 mHasSizeCompat = taskInfo.topActivityInSizeCompat; 87 mCameraCompatControlState = taskInfo.cameraCompatControlState; 88 mCompatUIHintsState = compatUIHintsState; 89 mCompatUIConfiguration = compatUIConfiguration; 90 mOnRestartButtonClicked = onRestartButtonClicked; 91 } 92 93 @Override getZOrder()94 protected int getZOrder() { 95 return Z_ORDER; 96 } 97 98 @Override getLayout()99 protected @Nullable View getLayout() { 100 return mLayout; 101 } 102 103 @Override removeLayout()104 protected void removeLayout() { 105 mLayout = null; 106 } 107 108 @Override eligibleToShowLayout()109 protected boolean eligibleToShowLayout() { 110 return mHasSizeCompat || shouldShowCameraControl(); 111 } 112 113 @Override createLayout()114 protected View createLayout() { 115 mLayout = inflateLayout(); 116 mLayout.inject(this); 117 118 updateVisibilityOfViews(); 119 120 if (mHasSizeCompat) { 121 mCallback.onSizeCompatRestartButtonAppeared(mTaskId); 122 } 123 124 return mLayout; 125 } 126 127 @VisibleForTesting inflateLayout()128 CompatUILayout inflateLayout() { 129 return (CompatUILayout) LayoutInflater.from(mContext).inflate(R.layout.compat_ui_layout, 130 null); 131 } 132 133 @Override updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow)134 public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, 135 boolean canShow) { 136 mTaskInfo = taskInfo; 137 final boolean prevHasSizeCompat = mHasSizeCompat; 138 final int prevCameraCompatControlState = mCameraCompatControlState; 139 mHasSizeCompat = taskInfo.topActivityInSizeCompat; 140 mCameraCompatControlState = taskInfo.cameraCompatControlState; 141 142 if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) { 143 return false; 144 } 145 146 if (prevHasSizeCompat != mHasSizeCompat 147 || prevCameraCompatControlState != mCameraCompatControlState) { 148 updateVisibilityOfViews(); 149 } 150 151 return true; 152 } 153 154 /** Called when the restart button is clicked. */ onRestartButtonClicked()155 void onRestartButtonClicked() { 156 mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener())); 157 } 158 159 /** Called when the camera treatment button is clicked. */ onCameraTreatmentButtonClicked()160 void onCameraTreatmentButtonClicked() { 161 if (!shouldShowCameraControl()) { 162 Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state."); 163 return; 164 } 165 // When a camera control is shown, only two states are allowed: "treament applied" and 166 // "treatment suggested". Clicks on the conrol's treatment button toggle between these 167 // two states. 168 mCameraCompatControlState = 169 mCameraCompatControlState == CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED 170 ? CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED 171 : CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; 172 mCallback.onCameraControlStateUpdated(mTaskId, mCameraCompatControlState); 173 mLayout.updateCameraTreatmentButton(mCameraCompatControlState); 174 } 175 176 /** Called when the camera dismiss button is clicked. */ onCameraDismissButtonClicked()177 void onCameraDismissButtonClicked() { 178 if (!shouldShowCameraControl()) { 179 Log.w(getTag(), "Camera compat shouldn't receive clicks in the hidden state."); 180 return; 181 } 182 mCameraCompatControlState = CAMERA_COMPAT_CONTROL_DISMISSED; 183 mCallback.onCameraControlStateUpdated(mTaskId, CAMERA_COMPAT_CONTROL_DISMISSED); 184 mLayout.setCameraControlVisibility(/* show= */ false); 185 } 186 187 /** Called when the restart button is long clicked. */ onRestartButtonLongClicked()188 void onRestartButtonLongClicked() { 189 if (mLayout == null) { 190 return; 191 } 192 mLayout.setSizeCompatHintVisibility(/* show= */ true); 193 } 194 195 /** Called when either dismiss or treatment camera buttons is long clicked. */ onCameraButtonLongClicked()196 void onCameraButtonLongClicked() { 197 if (mLayout == null) { 198 return; 199 } 200 mLayout.setCameraCompatHintVisibility(/* show= */ true); 201 } 202 203 @Override 204 @VisibleForTesting updateSurfacePosition()205 public void updateSurfacePosition() { 206 if (mLayout == null) { 207 return; 208 } 209 // Position of the button in the container coordinate. 210 final Rect taskBounds = getTaskBounds(); 211 final Rect taskStableBounds = getTaskStableBounds(); 212 final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL 213 ? taskStableBounds.left - taskBounds.left 214 : taskStableBounds.right - taskBounds.left - mLayout.getMeasuredWidth(); 215 final int positionY = taskStableBounds.bottom - taskBounds.top 216 - mLayout.getMeasuredHeight(); 217 // To secure a proper visualisation, we hide the layout while updating the position of 218 // the {@link SurfaceControl} it belongs. 219 final int oldVisibility = mLayout.getVisibility(); 220 if (oldVisibility == View.VISIBLE) { 221 mLayout.setVisibility(View.GONE); 222 } 223 updateSurfacePosition(positionX, positionY); 224 mLayout.setVisibility(oldVisibility); 225 } 226 updateVisibilityOfViews()227 private void updateVisibilityOfViews() { 228 if (mLayout == null) { 229 return; 230 } 231 // Size Compat mode restart button. 232 mLayout.setRestartButtonVisibility(mHasSizeCompat); 233 // Only show by default for the first time. 234 if (mHasSizeCompat && !mCompatUIHintsState.mHasShownSizeCompatHint) { 235 mLayout.setSizeCompatHintVisibility(/* show= */ true); 236 mCompatUIHintsState.mHasShownSizeCompatHint = true; 237 } 238 239 // Camera control for stretched issues. 240 mLayout.setCameraControlVisibility(shouldShowCameraControl()); 241 // Only show by default for the first time. 242 if (shouldShowCameraControl() && !mCompatUIHintsState.mHasShownCameraCompatHint) { 243 mLayout.setCameraCompatHintVisibility(/* show= */ true); 244 mCompatUIHintsState.mHasShownCameraCompatHint = true; 245 } 246 if (shouldShowCameraControl()) { 247 mLayout.updateCameraTreatmentButton(mCameraCompatControlState); 248 } 249 } 250 shouldShowCameraControl()251 private boolean shouldShowCameraControl() { 252 return mCameraCompatControlState != CAMERA_COMPAT_CONTROL_HIDDEN 253 && mCameraCompatControlState != CAMERA_COMPAT_CONTROL_DISMISSED; 254 } 255 256 /** 257 * A class holding the state of the compat UI hints, which is shared between all compat UI 258 * window managers. 259 */ 260 static class CompatUIHintsState { 261 @VisibleForTesting 262 boolean mHasShownSizeCompatHint; 263 @VisibleForTesting 264 boolean mHasShownCameraCompatHint; 265 } 266 } 267