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