• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.systemui.statusbar.phone;
18 
19 import android.app.AlertDialog;
20 import android.app.Dialog;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.Configuration;
26 import android.graphics.Insets;
27 import android.graphics.drawable.Drawable;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Looper;
31 import android.os.SystemProperties;
32 import android.os.UserHandle;
33 import android.util.TypedValue;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.ViewRootImpl;
37 import android.view.Window;
38 import android.view.WindowInsets.Type;
39 import android.view.WindowManager;
40 import android.view.WindowManager.LayoutParams;
41 
42 import androidx.annotation.Nullable;
43 import androidx.annotation.StyleRes;
44 
45 import com.android.systemui.Dependency;
46 import com.android.systemui.animation.DialogTransitionAnimator;
47 import com.android.systemui.broadcast.BroadcastDispatcher;
48 import com.android.systemui.dagger.qualifiers.Application;
49 import com.android.systemui.model.SysUiState;
50 import com.android.systemui.res.R;
51 import com.android.systemui.shared.system.QuickStepContract;
52 import com.android.systemui.util.DialogKt;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 
57 import javax.inject.Inject;
58 
59 /**
60  * Class for dialogs that should appear over panels and keyguard.
61  *
62  * DO NOT SUBCLASS THIS. See {@link DialogDelegate} for an interface that enables
63  * customizing behavior via composition instead of inheritance. Clients should implement the
64  * Delegate class and then pass their implementation into the SystemUIDialog constructor.
65  *
66  * Optionally provide a {@link SystemUIDialogManager} to its constructor to send signals to
67  * listeners on whether this dialog is showing.
68  *
69  * The SystemUIDialog registers a listener for the screen off / close system dialogs broadcast,
70  * and dismisses itself when it receives the broadcast.
71  */
72 public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigChangedCallback {
73     public static final int DEFAULT_THEME = R.style.Theme_SystemUI_Dialog;
74     // TODO(b/203389579): Remove this once the dialog width on large screens has been agreed on.
75     private static final String FLAG_TABLET_DIALOG_WIDTH =
76             "persist.systemui.flag_tablet_dialog_width";
77     public static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
78 
79     private final Context mContext;
80     private final DialogDelegate<SystemUIDialog> mDelegate;
81     @Nullable
82     private final DismissReceiver mDismissReceiver;
83     private final Handler mHandler = new Handler();
84     private final SystemUIDialogManager mDialogManager;
85     private final SysUiState mSysUiState;
86 
87     private int mLastWidth = Integer.MIN_VALUE;
88     private int mLastHeight = Integer.MIN_VALUE;
89     private int mLastConfigurationWidthDp = -1;
90     private int mLastConfigurationHeightDp = -1;
91 
92     private final List<Runnable> mOnCreateRunnables = new ArrayList<>();
93 
94     /**
95      * @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to
96      * {@link Factory#create(Delegate)} to create a custom dialog.
97      */
98     @Deprecated
SystemUIDialog(Context context)99     public SystemUIDialog(Context context) {
100         this(context, DEFAULT_THEME, DEFAULT_DISMISS_ON_DEVICE_LOCK);
101     }
102 
SystemUIDialog(Context context, int theme)103     public SystemUIDialog(Context context, int theme) {
104         this(context, theme, DEFAULT_DISMISS_ON_DEVICE_LOCK);
105     }
106 
SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock)107     public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
108         // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
109         // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
110         // the content and attach listeners.
111         this(context, theme, dismissOnDeviceLock,
112                 Dependency.get(SystemUIDialogManager.class),
113                 Dependency.get(SysUiState.class),
114                 Dependency.get(BroadcastDispatcher.class),
115                 Dependency.get(DialogTransitionAnimator.class));
116     }
117 
118     public static class Factory {
119         private final Context mContext;
120         private final SystemUIDialogManager mSystemUIDialogManager;
121         private final SysUiState mSysUiState;
122         private final BroadcastDispatcher mBroadcastDispatcher;
123         private final DialogTransitionAnimator mDialogTransitionAnimator;
124 
125         @Inject
Factory( @pplication Context context, SystemUIDialogManager systemUIDialogManager, SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogTransitionAnimator dialogTransitionAnimator)126         public Factory(
127                 @Application Context context,
128                 SystemUIDialogManager systemUIDialogManager,
129                 SysUiState sysUiState,
130                 BroadcastDispatcher broadcastDispatcher,
131                 DialogTransitionAnimator dialogTransitionAnimator) {
132             mContext = context;
133             mSystemUIDialogManager = systemUIDialogManager;
134             mSysUiState = sysUiState;
135             mBroadcastDispatcher = broadcastDispatcher;
136             mDialogTransitionAnimator = dialogTransitionAnimator;
137         }
138 
139         /**
140          * Creates a new instance of {@link SystemUIDialog} with no customized behavior.
141          *
142          * When you just need a dialog, call this.
143          */
create()144         public SystemUIDialog create() {
145             return create(new DialogDelegate<>() {
146             }, mContext, DEFAULT_THEME, true /* shouldAcsdDismissDialog */);
147         }
148 
149         /**
150          * Creates a new instance of {@link SystemUIDialog} with no customized behavior.
151          *
152          * When you just need a dialog created with a specific {@link Context}, call this.
153          */
create(Context context)154         public SystemUIDialog create(Context context) {
155             return create(new DialogDelegate<>() {
156             }, context, DEFAULT_THEME, true /* shouldAcsdDismissDialog */);
157         }
158 
159         /**
160          * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link
161          * Delegate}.
162          *
163          * When you need to customize the dialog, pass it a delegate.
164          */
create(Delegate delegate, Context context)165         public SystemUIDialog create(Delegate delegate, Context context) {
166             return create(delegate, context, DEFAULT_THEME);
167         }
168 
169         /**
170          * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link
171          * Delegate}. When you need to customize the dialog, pass it a delegate.
172          *
173          * This method allows the caller to specify if the dialog should be dismissed in response
174          * to the ACTION_CLOSE_SYSTEM_DIALOGS intent.
175          */
create(Delegate delegate, Context context, boolean shouldAcsdDismissDialog)176         public SystemUIDialog create(Delegate delegate, Context context,
177                 boolean shouldAcsdDismissDialog) {
178             return create(delegate, context, DEFAULT_THEME, shouldAcsdDismissDialog);
179         }
180 
create(Delegate delegate, Context context, @StyleRes int theme)181         public SystemUIDialog create(Delegate delegate, Context context, @StyleRes int theme) {
182             return create((DialogDelegate<SystemUIDialog>) delegate, context, theme,
183                     true /* shouldAcsdDismissDialog */);
184         }
185 
create(Delegate delegate)186         public SystemUIDialog create(Delegate delegate) {
187             return create(delegate, mContext);
188         }
189 
create(DialogDelegate<SystemUIDialog> dialogDelegate, Context context, @StyleRes int theme, boolean shouldAcsdDismissDialog)190         private SystemUIDialog create(DialogDelegate<SystemUIDialog> dialogDelegate,
191                 Context context, @StyleRes int theme, boolean shouldAcsdDismissDialog) {
192             return new SystemUIDialog(
193                     context,
194                     theme,
195                     DEFAULT_DISMISS_ON_DEVICE_LOCK,
196                     mSystemUIDialogManager,
197                     mSysUiState,
198                     mBroadcastDispatcher,
199                     mDialogTransitionAnimator,
200                     dialogDelegate,
201                     shouldAcsdDismissDialog);
202         }
203     }
204 
SystemUIDialog( Context context, int theme, boolean dismissOnDeviceLock, SystemUIDialogManager dialogManager, SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogTransitionAnimator dialogTransitionAnimator)205     public SystemUIDialog(
206             Context context,
207             int theme,
208             boolean dismissOnDeviceLock,
209             SystemUIDialogManager dialogManager,
210             SysUiState sysUiState,
211             BroadcastDispatcher broadcastDispatcher,
212             DialogTransitionAnimator dialogTransitionAnimator) {
213         this(
214                 context,
215                 theme,
216                 dismissOnDeviceLock,
217                 dialogManager,
218                 sysUiState,
219                 broadcastDispatcher,
220                 dialogTransitionAnimator,
221                 new DialogDelegate<>() {
222                 },
223                 true /* shouldAcsdDismissDialog */);
224     }
225 
SystemUIDialog( Context context, int theme, boolean dismissOnDeviceLock, SystemUIDialogManager dialogManager, SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogTransitionAnimator dialogTransitionAnimator, Delegate delegate)226     public SystemUIDialog(
227             Context context,
228             int theme,
229             boolean dismissOnDeviceLock,
230             SystemUIDialogManager dialogManager,
231             SysUiState sysUiState,
232             BroadcastDispatcher broadcastDispatcher,
233             DialogTransitionAnimator dialogTransitionAnimator,
234             Delegate delegate) {
235         this(
236                 context,
237                 theme,
238                 dismissOnDeviceLock,
239                 dialogManager,
240                 sysUiState,
241                 broadcastDispatcher,
242                 dialogTransitionAnimator,
243                 (DialogDelegate<SystemUIDialog>) delegate,
244                 true /* shouldAcsdDismissDialog */);
245     }
246 
SystemUIDialog( Context context, int theme, boolean dismissOnDeviceLock, SystemUIDialogManager dialogManager, SysUiState sysUiState, BroadcastDispatcher broadcastDispatcher, DialogTransitionAnimator dialogTransitionAnimator, DialogDelegate<SystemUIDialog> delegate, boolean shouldAcsdDismissDialog)247     public SystemUIDialog(
248             Context context,
249             int theme,
250             boolean dismissOnDeviceLock,
251             SystemUIDialogManager dialogManager,
252             SysUiState sysUiState,
253             BroadcastDispatcher broadcastDispatcher,
254             DialogTransitionAnimator dialogTransitionAnimator,
255             DialogDelegate<SystemUIDialog> delegate,
256             boolean shouldAcsdDismissDialog) {
257         super(context, theme);
258         mContext = context;
259         mDelegate = delegate;
260 
261         applyFlags(this);
262         WindowManager.LayoutParams attrs = getWindow().getAttributes();
263         attrs.setTitle(getClass().getSimpleName());
264         getWindow().setAttributes(attrs);
265 
266         mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher,
267                 dialogTransitionAnimator, shouldAcsdDismissDialog) : null;
268         mDialogManager = dialogManager;
269         mSysUiState = sysUiState;
270     }
271 
272     @Override
onCreate(Bundle savedInstanceState)273     protected void onCreate(Bundle savedInstanceState) {
274         mDelegate.beforeCreate(this, savedInstanceState);
275         super.onCreate(savedInstanceState);
276         mDelegate.onCreate(this, savedInstanceState);
277 
278         Configuration config = getContext().getResources().getConfiguration();
279         mLastConfigurationWidthDp = config.screenWidthDp;
280         mLastConfigurationHeightDp = config.screenHeightDp;
281         updateWindowSize();
282 
283         for (int i = 0; i < mOnCreateRunnables.size(); i++) {
284             mOnCreateRunnables.get(i).run();
285         }
286         View targetView = getWindow().getDecorView();
287         DialogKt.registerAnimationOnBackInvoked(
288                 /* dialog = */ this,
289                 /* targetView = */ targetView,
290                 /* backAnimationSpec= */mDelegate.getBackAnimationSpec(
291                         () -> targetView.getResources().getDisplayMetrics())
292         );
293     }
294 
updateWindowSize()295     private void updateWindowSize() {
296         // Only the thread that created this dialog can update its window size.
297         if (Looper.myLooper() != mHandler.getLooper()) {
298             mHandler.post(this::updateWindowSize);
299             return;
300         }
301 
302         int width = getWidth();
303         int height = getHeight();
304         if (width == mLastWidth && height == mLastHeight) {
305             return;
306         }
307 
308         mLastWidth = width;
309         mLastHeight = height;
310         getWindow().setLayout(width, height);
311     }
312 
313     @Override
onConfigurationChanged(Configuration configuration)314     public void onConfigurationChanged(Configuration configuration) {
315         if (mLastConfigurationWidthDp != configuration.screenWidthDp
316                 || mLastConfigurationHeightDp != configuration.screenHeightDp) {
317             mLastConfigurationWidthDp = configuration.screenWidthDp;
318             mLastConfigurationHeightDp = configuration.compatScreenWidthDp;
319 
320             updateWindowSize();
321         }
322 
323         mDelegate.onConfigurationChanged(this, configuration);
324     }
325 
326     /**
327      * Return this dialog width. This method will be invoked when this dialog is created and when
328      * the device configuration changes, and the result will be used to resize this dialog window.
329      */
getWidth()330     protected int getWidth() {
331         return mDelegate.getWidth(this);
332     }
333 
334     /**
335      * Return this dialog height. This method will be invoked when this dialog is created and when
336      * the device configuration changes, and the result will be used to resize this dialog window.
337      */
getHeight()338     protected int getHeight() {
339         return mDelegate.getHeight(this);
340     }
341 
342     @Override
onStart()343     protected final void onStart() {
344         super.onStart();
345 
346         if (mDismissReceiver != null) {
347             mDismissReceiver.register();
348         }
349 
350         // Listen for configuration changes to resize this dialog window. This is mostly necessary
351         // for foldables that often go from large <=> small screen when folding/unfolding.
352         ViewRootImpl.addConfigCallback(this);
353         mDialogManager.setShowing(this, true);
354         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
355                 .commitUpdate(mContext.getDisplayId());
356 
357         mDelegate.onStart(this);
358         start();
359     }
360 
361     /**
362      * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()}
363      * should override this method instead.
364      */
start()365     protected void start() {
366         // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
367         // Instead, add things to onStop above.
368     }
369 
370     @Override
onStop()371     protected final void onStop() {
372         super.onStop();
373 
374         if (mDismissReceiver != null) {
375             mDismissReceiver.unregister();
376         }
377 
378         ViewRootImpl.removeConfigCallback(this);
379         mDialogManager.setShowing(this, false);
380         mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false)
381                 .commitUpdate(mContext.getDisplayId());
382 
383         mDelegate.onStop(this);
384         stop();
385     }
386 
387     /**
388      * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()}
389      * should override this method instead.
390      */
stop()391     protected void stop() {
392         // IMPORTANT: Please do not add anything here, since subclasses are likely to override this.
393         // Instead, add things to onStop above.
394     }
395 
396     @Override
onWindowFocusChanged(boolean hasFocus)397     public void onWindowFocusChanged(boolean hasFocus) {
398         super.onWindowFocusChanged(hasFocus);
399         mDelegate.onWindowFocusChanged(this, hasFocus);
400         if (hasFocus) {
401             // Update SysUI state to reflect that a dialog is showing. This ensures the state is
402             // correct when this dialog regains focus after another dialog was closed.
403             // See b/386871258
404             mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true)
405                     .commitUpdate(mContext.getDisplayId());
406         }
407     }
408 
setShowForAllUsers(boolean show)409     public void setShowForAllUsers(boolean show) {
410         setShowForAllUsers(this, show);
411     }
412 
setMessage(int resId)413     public void setMessage(int resId) {
414         setMessage(mContext.getString(resId));
415     }
416 
417     /**
418      * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog
419      * will automatically be dismissed when the button is clicked.
420      */
setPositiveButton(int resId, OnClickListener onClick)421     public void setPositiveButton(int resId, OnClickListener onClick) {
422         setPositiveButton(resId, onClick, true /* dismissOnClick */);
423     }
424 
425     /**
426      * Set a listener to be invoked when the positive button of the dialog is pressed. The dialog
427      * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
428      */
setPositiveButton(int resId, OnClickListener onClick, boolean dismissOnClick)429     public void setPositiveButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
430         setButton(BUTTON_POSITIVE, resId, onClick, dismissOnClick);
431     }
432 
433     /**
434      * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog
435      * will automatically be dismissed when the button is clicked.
436      */
setNegativeButton(int resId, OnClickListener onClick)437     public void setNegativeButton(int resId, OnClickListener onClick) {
438         setNegativeButton(resId, onClick, true /* dismissOnClick */);
439     }
440 
441     /**
442      * Set a listener to be invoked when the negative button of the dialog is pressed. The dialog
443      * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
444      */
setNegativeButton(int resId, OnClickListener onClick, boolean dismissOnClick)445     public void setNegativeButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
446         setButton(BUTTON_NEGATIVE, resId, onClick, dismissOnClick);
447     }
448 
449     /**
450      * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog
451      * will automatically be dismissed when the button is clicked.
452      */
setNeutralButton(int resId, OnClickListener onClick)453     public void setNeutralButton(int resId, OnClickListener onClick) {
454         setNeutralButton(resId, onClick, true /* dismissOnClick */);
455     }
456 
457     /**
458      * Set a listener to be invoked when the neutral button of the dialog is pressed. The dialog
459      * will be dismissed when the button is clicked iff {@code dismissOnClick} is true.
460      */
setNeutralButton(int resId, OnClickListener onClick, boolean dismissOnClick)461     public void setNeutralButton(int resId, OnClickListener onClick, boolean dismissOnClick) {
462         setButton(BUTTON_NEUTRAL, resId, onClick, dismissOnClick);
463     }
464 
setButton(int whichButton, int resId, OnClickListener onClick, boolean dismissOnClick)465     private void setButton(int whichButton, int resId, OnClickListener onClick,
466             boolean dismissOnClick) {
467         if (dismissOnClick) {
468             setButton(whichButton, mContext.getString(resId), onClick);
469         } else {
470             // Set a null OnClickListener to make sure the button is still created and shown.
471             setButton(whichButton, mContext.getString(resId), (OnClickListener) null);
472 
473             // When the dialog is created, set the click listener but don't dismiss the dialog when
474             // it is clicked.
475             mOnCreateRunnables.add(() -> getButton(whichButton).setOnClickListener(
476                     view -> onClick.onClick(this, whichButton)));
477         }
478     }
479 
setShowForAllUsers(Dialog dialog, boolean show)480     public static void setShowForAllUsers(Dialog dialog, boolean show) {
481         if (show) {
482             dialog.getWindow().getAttributes().privateFlags |=
483                     WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
484         } else {
485             dialog.getWindow().getAttributes().privateFlags &=
486                     ~WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
487         }
488     }
489 
490     /**
491      * Ensure the window type is set properly to show over all other screens
492      */
setWindowOnTop(Dialog dialog, boolean isKeyguardShowing)493     public static void setWindowOnTop(Dialog dialog, boolean isKeyguardShowing) {
494         final Window window = dialog.getWindow();
495         window.setType(LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
496         if (isKeyguardShowing) {
497             window.getAttributes().setFitInsetsTypes(
498                     window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
499         }
500     }
501 
applyFlags(AlertDialog dialog)502     public static AlertDialog applyFlags(AlertDialog dialog) {
503         return applyFlags(dialog, true);
504     }
505 
applyFlags(AlertDialog dialog, boolean showWhenLocked)506     public static AlertDialog applyFlags(AlertDialog dialog, boolean showWhenLocked) {
507         final Window window = dialog.getWindow();
508         window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
509         window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
510         if (showWhenLocked) {
511             window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
512         }
513         window.getAttributes().setFitInsetsTypes(
514                 window.getAttributes().getFitInsetsTypes() & ~Type.statusBars());
515         return dialog;
516     }
517 
518     /**
519      * Registers a listener that dismisses the given dialog when it receives
520      * the screen off / close system dialogs broadcast.
521      * <p>
522      * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
523      * calling this because it causes a leak of BroadcastReceiver. Instead, call the version that
524      * takes an extra Runnable as a parameter.
525      *
526      * @param dialog The dialog to be associated with the listener.
527      */
registerDismissListener(Dialog dialog)528     public static void registerDismissListener(Dialog dialog) {
529         registerDismissListener(dialog, null);
530     }
531 
532     /**
533      * Registers a listener that dismisses the given dialog when it receives
534      * the screen off / close system dialogs broadcast.
535      * <p>
536      * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
537      * calling this because it causes a leak of BroadcastReceiver.
538      *
539      * @param dialog        The dialog to be associated with the listener.
540      * @param dismissAction An action to run when the dialog is dismissed.
541      */
registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction)542     public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
543         // TODO(b/219008720): Remove those calls to Dependency.get.
544         DismissReceiver dismissReceiver = new DismissReceiver(dialog,
545                 Dependency.get(BroadcastDispatcher.class),
546                 Dependency.get(DialogTransitionAnimator.class),
547                 true /* shouldAcsdDismissDialog */);
548         dialog.setOnDismissListener(d -> {
549             dismissReceiver.unregister();
550             if (dismissAction != null) dismissAction.run();
551         });
552         dismissReceiver.register();
553     }
554 
555     /** Set an appropriate size to {@code dialog} depending on the current configuration. */
setDialogSize(Dialog dialog)556     public static void setDialogSize(Dialog dialog) {
557         // We need to create the dialog first, otherwise the size will be overridden when it is
558         // created.
559         dialog.create();
560         dialog.getWindow().setLayout(getDefaultDialogWidth(dialog), getDefaultDialogHeight());
561     }
562 
getDefaultDialogWidth(Dialog dialog)563     static int getDefaultDialogWidth(Dialog dialog) {
564         Context context = dialog.getContext();
565         int flagValue = SystemProperties.getInt(FLAG_TABLET_DIALOG_WIDTH, 0);
566         if (flagValue == -1) {
567             // The width of bottom sheets (624dp).
568             return calculateDialogWidthWithInsets(dialog, 624);
569         } else if (flagValue == -2) {
570             // The suggested small width for all dialogs (348dp)
571             return calculateDialogWidthWithInsets(dialog, 348);
572         } else if (flagValue > 0) {
573             // Any given width.
574             return calculateDialogWidthWithInsets(dialog, flagValue);
575         } else {
576             // By default we use the same width as the notification shade in portrait mode.
577             int width = context.getResources().getDimensionPixelSize(R.dimen.large_dialog_width);
578             if (width > 0) {
579                 // If we are neither WRAP_CONTENT or MATCH_PARENT, add the background insets so that
580                 // the dialog is the desired width.
581                 width += getHorizontalInsets(dialog);
582             }
583             return width;
584         }
585     }
586 
587     /**
588      * Return the pixel width {@param dialog} should be so that it is {@param widthInDp} wide,
589      * taking its background insets into consideration.
590      */
calculateDialogWidthWithInsets(Dialog dialog, int widthInDp)591     private static int calculateDialogWidthWithInsets(Dialog dialog, int widthInDp) {
592         float widthInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthInDp,
593                 dialog.getContext().getResources().getDisplayMetrics());
594         return Math.round(widthInPixels + getHorizontalInsets(dialog));
595     }
596 
getHorizontalInsets(Dialog dialog)597     private static int getHorizontalInsets(Dialog dialog) {
598         View decorView = dialog.getWindow().getDecorView();
599         if (decorView == null) {
600             return 0;
601         }
602 
603         // We first look for the background on the dialogContentWithBackground added by
604         // DialogTransitionAnimator. If it's not there, we use the background of the DecorView.
605         View viewWithBackground = decorView.findViewByPredicate(
606                 view -> view.getTag(
607                         com.android.systemui.animation.R.id.tag_dialog_background) != null);
608         Drawable background = viewWithBackground != null ? viewWithBackground.getBackground()
609                 : decorView.getBackground();
610         Insets insets = background != null ? background.getOpticalInsets() : Insets.NONE;
611         return insets.left + insets.right;
612     }
613 
getDefaultDialogHeight()614     static int getDefaultDialogHeight() {
615         return ViewGroup.LayoutParams.WRAP_CONTENT;
616     }
617 
618     private static class DismissReceiver extends BroadcastReceiver {
619         private final IntentFilter mIntentFilter = new IntentFilter();
620 
621         private final Dialog mDialog;
622         private boolean mRegistered;
623         private final BroadcastDispatcher mBroadcastDispatcher;
624         private final DialogTransitionAnimator mDialogTransitionAnimator;
625 
DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher, DialogTransitionAnimator dialogTransitionAnimator, boolean shouldAcsdDismissDialog)626         DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher,
627                 DialogTransitionAnimator dialogTransitionAnimator,
628                 boolean shouldAcsdDismissDialog) {
629             mDialog = dialog;
630             mBroadcastDispatcher = broadcastDispatcher;
631             mDialogTransitionAnimator = dialogTransitionAnimator;
632 
633             mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
634             if (shouldAcsdDismissDialog) {
635                 mIntentFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
636             }
637         }
638 
register()639         void register() {
640             mBroadcastDispatcher.registerReceiver(this, mIntentFilter, null, UserHandle.CURRENT);
641             mRegistered = true;
642         }
643 
unregister()644         void unregister() {
645             if (mRegistered) {
646                 mBroadcastDispatcher.unregisterReceiver(this);
647                 mRegistered = false;
648             }
649         }
650 
651         @Override
onReceive(Context context, Intent intent)652         public void onReceive(Context context, Intent intent) {
653             // These broadcast are usually received when locking the device, swiping up to home
654             // (which collapses the shade), etc. In those cases, we usually don't want to animate
655             // back into the view.
656             mDialogTransitionAnimator.disableAllCurrentDialogsExitAnimations();
657             mDialog.dismiss();
658         }
659     }
660 
661     /**
662      * A delegate class that should be implemented in place of sublcassing {@link SystemUIDialog}.
663      *
664      * Implement this interface and then pass an instance of your implementation to
665      * {@link SystemUIDialog.Factory#create(Delegate)}.
666      */
667     public interface Delegate extends DialogDelegate<SystemUIDialog> {
668         /**
669          * Returns a new {@link SystemUIDialog} which has been passed this Delegate in its
670          * construction.
671          */
createDialog()672         SystemUIDialog createDialog();
673     }
674 }
675