• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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