1 /*
<lambda>null2 * Copyright (C) 2023 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.util
18
19 import android.app.Dialog
20 import android.view.View
21 import android.view.ViewGroup
22 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
23 import android.widget.FrameLayout
24 import android.window.OnBackInvokedDispatcher
25 import com.android.systemui.animation.back.BackAnimationSpec
26 import com.android.systemui.animation.back.BackTransformation
27 import com.android.systemui.animation.back.applyTo
28 import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
29 import com.android.systemui.animation.back.onBackAnimationCallbackFrom
30 import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached
31 import com.android.systemui.animation.view.LaunchableFrameLayout
32
33 /**
34 * Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec].
35 * The [BackTransformation] will be applied on the [targetView].
36 */
37 @JvmOverloads
38 fun Dialog.registerAnimationOnBackInvoked(
39 targetView: View,
40 backAnimationSpec: BackAnimationSpec =
41 BackAnimationSpec.floatingSystemSurfacesForSysUi(
42 displayMetricsProvider = { targetView.resources.displayMetrics },
43 ),
44 ) {
45 targetView.registerOnBackInvokedCallbackOnViewAttached(
46 onBackInvokedDispatcher = onBackInvokedDispatcher,
47 onBackAnimationCallback =
48 onBackAnimationCallbackFrom(
49 backAnimationSpec = backAnimationSpec,
50 displayMetrics = targetView.resources.displayMetrics,
backTransformationnull51 onBackProgressed = { backTransformation -> backTransformation.applyTo(targetView) },
<lambda>null52 onBackInvoked = { dismiss() },
53 ),
54 )
55 }
56
57 /**
58 * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate
59 * outside its bounds. No-op if the dialog is already fullscreen.
60 *
61 * <p>Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view
62 * and a layout listener. The new view matches the original dialog DecorView in size, position, and
63 * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The
64 * layout listener is listening to changes to the modified DecorView. It is the responsibility of
65 * the caller to deregister the listener when the dialog is dismissed.
66 */
maybeForceFullscreennull67 fun Dialog.maybeForceFullscreen(): Pair<LaunchableFrameLayout, View.OnLayoutChangeListener>? {
68 // Create the dialog so that its onCreate() method is called, which usually sets the dialog
69 // content.
70 create()
71
72 val window = window!!
73 val decorView = window.decorView as ViewGroup
74
75 val isWindowFullscreen =
76 window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
77 if (isWindowFullscreen) {
78 return null
79 }
80
81 // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible
82 // to animate outside its bounds.
83 //
84 // Before that, we add a new View as a child of the DecorView with the same size and gravity as
85 // that DecorView, then we add all original children of the DecorView to that new View. Finally
86 // we remove the background of the DecorView and add it to the new View, then we make the
87 // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window.
88 //
89 // On top of that, we also add a fullscreen transparent background between the DecorView and the
90 // view that we added so that we can dismiss the dialog when this view is clicked. This is
91 // necessary because DecorView overrides onTouchEvent and therefore we can't set the click
92 // listener directly on the (now fullscreen) DecorView.
93 val fullscreenTransparentBackground = FrameLayout(context)
94 decorView.addView(
95 fullscreenTransparentBackground,
96 0 /* index */,
97 FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
98 )
99
100 val dialogContentWithBackground = LaunchableFrameLayout(context)
101 dialogContentWithBackground.background = decorView.background
102
103 // Make the window background transparent. Note that setting the window (or DecorView)
104 // background drawable to null leads to issues with background color (not being transparent) or
105 // with insets that are not refreshed. Therefore we need to set it to something not null, hence
106 // we are using android.R.color.transparent here.
107 window.setBackgroundDrawableResource(android.R.color.transparent)
108
109 // Close the dialog when clicking outside of it.
110 fullscreenTransparentBackground.setOnClickListener { dismiss() }
111 dialogContentWithBackground.isClickable = true
112
113 // Make sure the transparent and dialog backgrounds are not focusable by accessibility
114 // features.
115 fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
116 dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
117
118 fullscreenTransparentBackground.addView(
119 dialogContentWithBackground,
120 FrameLayout.LayoutParams(
121 window.attributes.width,
122 window.attributes.height,
123 window.attributes.gravity
124 )
125 )
126
127 // Move all original children of the DecorView to the new View we just added.
128 for (i in 1 until decorView.childCount) {
129 val view = decorView.getChildAt(1)
130 decorView.removeViewAt(1)
131 dialogContentWithBackground.addView(view)
132 }
133
134 // Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
135 window.setLayout(MATCH_PARENT, MATCH_PARENT)
136 val decorViewLayoutListener =
137 View.OnLayoutChangeListener {
138 v,
139 left,
140 top,
141 right,
142 bottom,
143 oldLeft,
144 oldTop,
145 oldRight,
146 oldBottom ->
147 if (
148 window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT
149 ) {
150 // The dialog size changed, copy its size to dialogContentWithBackground and make
151 // the dialog window full screen again.
152 val layoutParams = dialogContentWithBackground.layoutParams
153 layoutParams.width = window.attributes.width
154 layoutParams.height = window.attributes.height
155 dialogContentWithBackground.layoutParams = layoutParams
156 window.setLayout(MATCH_PARENT, MATCH_PARENT)
157 }
158 }
159 decorView.addOnLayoutChangeListener(decorViewLayoutListener)
160
161 return dialogContentWithBackground to decorViewLayoutListener
162 }
163