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