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