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