• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
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.provider.Settings;
27 import android.util.Pair;
28 import android.view.LayoutInflater;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.WindowManager;
32 import android.view.accessibility.AccessibilityEvent;
33 
34 import com.android.internal.annotations.VisibleForTesting;
35 import com.android.wm.shell.R;
36 import com.android.wm.shell.ShellTaskOrganizer;
37 import com.android.wm.shell.common.DisplayLayout;
38 import com.android.wm.shell.common.SyncTransactionQueue;
39 import com.android.wm.shell.transition.Transitions;
40 
41 import java.util.function.Consumer;
42 
43 /**
44  * Window manager for the Restart Dialog.
45  *
46  * TODO(b/263484314): Create abstraction of RestartDialogWindowManager and LetterboxEduWindowManager
47  */
48 class RestartDialogWindowManager extends CompatUIWindowManagerAbstract {
49 
50     /**
51      * The restart dialog should be the topmost child of the Task in case there can be more
52      * than one child.
53      */
54     private static final int Z_ORDER = Integer.MAX_VALUE;
55 
56     private final DialogAnimationController<RestartDialogLayout> mAnimationController;
57 
58     private final Transitions mTransitions;
59 
60     // Remember the last reported state in case visibility changes due to keyguard or IME updates.
61     private boolean mRequestRestartDialog;
62 
63     private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnDismissCallback;
64 
65     private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartCallback;
66 
67     private final CompatUIConfiguration mCompatUIConfiguration;
68 
69     /**
70      * The vertical margin between the dialog container and the task stable bounds (excluding
71      * insets).
72      */
73     private final int mDialogVerticalMargin;
74 
75     @NonNull
76     private TaskInfo mTaskInfo;
77 
78     @Nullable
79     @VisibleForTesting
80     RestartDialogLayout mLayout;
81 
RestartDialogWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, Transitions transitions, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, CompatUIConfiguration compatUIConfiguration)82     RestartDialogWindowManager(Context context, TaskInfo taskInfo,
83             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
84             DisplayLayout displayLayout, Transitions transitions,
85             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
86             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
87             CompatUIConfiguration compatUIConfiguration) {
88         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
89                 onRestartCallback, onDismissCallback,
90                 new DialogAnimationController<>(context, "RestartDialogWindowManager"),
91                 compatUIConfiguration);
92     }
93 
94     @VisibleForTesting
RestartDialogWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, Transitions transitions, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback, Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback, DialogAnimationController<RestartDialogLayout> animationController, CompatUIConfiguration compatUIConfiguration)95     RestartDialogWindowManager(Context context, TaskInfo taskInfo,
96             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
97             DisplayLayout displayLayout, Transitions transitions,
98             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartCallback,
99             Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onDismissCallback,
100             DialogAnimationController<RestartDialogLayout> animationController,
101             CompatUIConfiguration compatUIConfiguration) {
102         super(context, taskInfo, syncQueue, taskListener, displayLayout);
103         mTaskInfo = taskInfo;
104         mTransitions = transitions;
105         mOnDismissCallback = onDismissCallback;
106         mOnRestartCallback = onRestartCallback;
107         mAnimationController = animationController;
108         mDialogVerticalMargin = (int) mContext.getResources().getDimension(
109                 R.dimen.letterbox_restart_dialog_margin);
110         mCompatUIConfiguration = compatUIConfiguration;
111     }
112 
113     @Override
getZOrder()114     protected int getZOrder() {
115         return Z_ORDER;
116     }
117 
118     @Override
119     @Nullable
getLayout()120     protected  View getLayout() {
121         return mLayout;
122     }
123 
124     @Override
removeLayout()125     protected void removeLayout() {
126         mLayout = null;
127     }
128 
129     @Override
eligibleToShowLayout()130     protected boolean eligibleToShowLayout() {
131         // We don't show this dialog if the user has explicitly selected so clicking on a checkbox.
132         return mRequestRestartDialog && !isTaskbarEduShowing() && (mLayout != null
133                 || mCompatUIConfiguration.shouldShowRestartDialogAgain(mTaskInfo));
134     }
135 
136     @Override
createLayout()137     protected View createLayout() {
138         mLayout = inflateLayout();
139         updateDialogMargins();
140 
141         // startEnterAnimation will be called immediately if shell-transitions are disabled.
142         mTransitions.runOnIdle(this::startEnterAnimation);
143 
144         return mLayout;
145     }
146 
setRequestRestartDialog(boolean enabled)147     void setRequestRestartDialog(boolean enabled) {
148         mRequestRestartDialog = enabled;
149     }
150 
151     @Override
updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener, boolean canShow)152     public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
153             boolean canShow) {
154         mTaskInfo = taskInfo;
155         return super.updateCompatInfo(taskInfo, taskListener, canShow);
156     }
157 
needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener)158     boolean needsToBeRecreated(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
159         return taskInfo.configuration.uiMode != mTaskInfo.configuration.uiMode
160                 || !getTaskListener().equals(taskListener);
161     }
162 
updateDialogMargins()163     private void updateDialogMargins() {
164         if (mLayout == null) {
165             return;
166         }
167         final View dialogContainer = mLayout.getDialogContainerView();
168         ViewGroup.MarginLayoutParams marginParams =
169                 (ViewGroup.MarginLayoutParams) dialogContainer.getLayoutParams();
170 
171         final Rect taskBounds = getTaskBounds();
172         final Rect taskStableBounds = getTaskStableBounds();
173 
174         marginParams.topMargin = taskStableBounds.top - taskBounds.top + mDialogVerticalMargin;
175         marginParams.bottomMargin =
176                 taskBounds.bottom - taskStableBounds.bottom + mDialogVerticalMargin;
177         dialogContainer.setLayoutParams(marginParams);
178     }
179 
inflateLayout()180     private RestartDialogLayout inflateLayout() {
181         return (RestartDialogLayout) LayoutInflater.from(mContext).inflate(
182                 R.layout.letterbox_restart_dialog_layout, null);
183     }
184 
startEnterAnimation()185     private void startEnterAnimation() {
186         if (mLayout == null) {
187             // Dialog has already been released.
188             return;
189         }
190         mAnimationController.startEnterAnimation(mLayout, /* endCallback= */
191                 this::onDialogEnterAnimationEnded);
192     }
193 
onDialogEnterAnimationEnded()194     private void onDialogEnterAnimationEnded() {
195         if (mLayout == null) {
196             // Dialog has already been released.
197             return;
198         }
199         mLayout.setDismissOnClickListener(this::onDismiss);
200         mLayout.setRestartOnClickListener(dontShowAgain -> {
201             if (mLayout != null) {
202                 mLayout.setDismissOnClickListener(null);
203                 mAnimationController.startExitAnimation(mLayout, () -> {
204                     release();
205                 });
206             }
207             if (dontShowAgain) {
208                 mCompatUIConfiguration.setDontShowRestartDialogAgain(mTaskInfo);
209             }
210             mOnRestartCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
211         });
212         // Focus on the dialog title for accessibility.
213         mLayout.getDialogTitle().sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
214     }
215 
onDismiss()216     private void onDismiss() {
217         if (mLayout == null) {
218             return;
219         }
220 
221         mLayout.setDismissOnClickListener(null);
222         mAnimationController.startExitAnimation(mLayout, () -> {
223             release();
224             mOnDismissCallback.accept(Pair.create(mTaskInfo, getTaskListener()));
225         });
226     }
227 
228     @Override
release()229     public void release() {
230         mAnimationController.cancelAnimation();
231         super.release();
232     }
233 
234     @Override
onParentBoundsChanged()235     protected void onParentBoundsChanged() {
236         if (mLayout == null) {
237             return;
238         }
239         // Both the layout dimensions and dialog margins depend on the parent bounds.
240         WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
241         mLayout.setLayoutParams(windowLayoutParams);
242         updateDialogMargins();
243         relayout(windowLayoutParams);
244     }
245 
246     @Override
updateSurfacePosition()247     protected void updateSurfacePosition() {
248         // Nothing to do, since the position of the surface is fixed to the top left corner (0,0)
249         // of the task (parent surface), which is the default position of a surface.
250     }
251 
252     @Override
getWindowLayoutParams()253     protected WindowManager.LayoutParams getWindowLayoutParams() {
254         final Rect taskBounds = getTaskBounds();
255         return getWindowLayoutParams(/* width= */ taskBounds.width(), /* height= */
256                 taskBounds.height());
257     }
258 
259     @VisibleForTesting
isTaskbarEduShowing()260     boolean isTaskbarEduShowing() {
261         return Settings.Secure.getInt(mContext.getContentResolver(),
262                 LAUNCHER_TASKBAR_EDUCATION_SHOWING, /* def= */ 0) == 1;
263     }
264 }
265