• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 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.controls.ui
18 
19 import android.app.ActivityOptions
20 import android.app.ActivityTaskManager
21 import android.app.ActivityTaskManager.INVALID_TASK_ID
22 import android.app.Dialog
23 import android.app.PendingIntent
24 import android.content.ComponentName
25 import android.content.Context
26 import android.content.Intent
27 import android.graphics.Rect
28 import android.view.View
29 import android.view.ViewGroup
30 import android.view.WindowInsets
31 import android.view.WindowInsets.Type
32 import android.view.WindowManager
33 import android.widget.ImageView
34 import com.android.internal.policy.ScreenDecorationsUtils
35 import com.android.systemui.R
36 import com.android.systemui.broadcast.BroadcastSender
37 import com.android.systemui.plugins.ActivityStarter
38 import com.android.systemui.statusbar.policy.KeyguardStateController
39 import com.android.wm.shell.TaskView
40 
41 /**
42  * A dialog that provides an {@link TaskView}, allowing the application to provide
43  * additional information and actions pertaining to a {@link android.service.controls.Control}.
44  * The activity being launched is specified by {@link android.service.controls.Control#getAppIntent}.
45  */
46 class DetailDialog(
47     val activityContext: Context,
48     val broadcastSender: BroadcastSender,
49     val taskView: TaskView,
50     val pendingIntent: PendingIntent,
51     val cvh: ControlViewHolder,
52     val keyguardStateController: KeyguardStateController,
53     val activityStarter: ActivityStarter
54 ) : Dialog(
55     activityContext,
56     R.style.Theme_SystemUI_Dialog_Control_DetailPanel
57 ) {
58     companion object {
59         /*
60          * Indicate to the activity that it is being rendered in a bottomsheet, and they
61          * should optimize the layout for a smaller space.
62          */
63         private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL"
64     }
65 
66     var detailTaskId = INVALID_TASK_ID
67     private lateinit var taskViewContainer: View
68     private val taskWidthPercentWidth = activityContext.resources.getFloat(
69         R.dimen.controls_task_view_width_percentage
70     )
71 
72     private val fillInIntent = Intent().apply {
73         putExtra(EXTRA_USE_PANEL, true)
74 
75         // Apply flags to make behaviour match documentLaunchMode=always.
76         addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
77         addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
78     }
79 
80     fun removeDetailTask() {
81         if (detailTaskId == INVALID_TASK_ID) return
82         ActivityTaskManager.getInstance().removeTask(detailTaskId)
83         detailTaskId = INVALID_TASK_ID
84     }
85 
86     val stateCallback = object : TaskView.Listener {
87         override fun onInitialized() {
88             taskViewContainer.apply {
89                 // For some devices, limit the overall width of the taskView
90                 val lp = getLayoutParams()
91                 lp.width = (getWidth() * taskWidthPercentWidth).toInt()
92                 setLayoutParams(lp)
93             }
94 
95             val options = ActivityOptions.makeCustomAnimation(
96                 activityContext,
97                 0 /* enterResId */,
98                 0 /* exitResId */
99             )
100             taskView.startActivity(
101                 pendingIntent,
102                 fillInIntent,
103                 options,
104                 getTaskViewBounds()
105             )
106         }
107 
108         override fun onTaskRemovalStarted(taskId: Int) {
109             detailTaskId = INVALID_TASK_ID
110             dismiss()
111         }
112 
113         override fun onTaskCreated(taskId: Int, name: ComponentName?) {
114             detailTaskId = taskId
115             requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
116                 setAlpha(1f)
117             }
118         }
119 
120         override fun onReleased() {
121             removeDetailTask()
122         }
123 
124         override fun onBackPressedOnTaskRoot(taskId: Int) {
125             dismiss()
126         }
127     }
128 
129     init {
130         // To pass touches to the task inside TaskView.
131         window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
132         window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
133 
134         setContentView(R.layout.controls_detail_dialog)
135 
136         taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container)
137 
138         requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
139             addView(taskView)
140             setAlpha(0f)
141         }
142 
143         requireViewById<ImageView>(R.id.control_detail_close).apply {
144             setOnClickListener { _: View -> dismiss() }
145         }
146         requireViewById<View>(R.id.control_detail_root).apply {
147             setOnClickListener { _: View -> dismiss() }
148         }
149 
150         requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
151             setOnClickListener { v: View ->
152                 removeDetailTask()
153                 dismiss()
154 
155                 val action = ActivityStarter.OnDismissAction {
156                     // Remove the task explicitly, since onRelease() callback will be executed after
157                     // startActivity() below is called.
158                     broadcastSender.closeSystemDialogs()
159                     pendingIntent.send()
160                     false
161                 }
162                 if (keyguardStateController.isUnlocked()) {
163                     action.onDismiss()
164                 } else {
165                     activityStarter.dismissKeyguardThenExecute(
166                         action,
167                         null /* cancel */,
168                         true /* afterKeyguardGone */
169                     )
170                 }
171             }
172         }
173 
174         // consume all insets to achieve slide under effect
175         window.getDecorView().setOnApplyWindowInsetsListener {
176             v: View, insets: WindowInsets ->
177                 val l = v.getPaddingLeft()
178                 val r = v.getPaddingRight()
179                 val insets = insets.getInsets(Type.systemBars())
180                 v.setPadding(l, insets.top, r, insets.bottom)
181 
182                 WindowInsets.CONSUMED
183         }
184 
185         if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(context.getResources())) {
186             val cornerRadius = context.resources
187                 .getDimensionPixelSize(R.dimen.controls_activity_view_corner_radius)
188             taskView.setCornerRadius(cornerRadius.toFloat())
189         }
190 
191         taskView.setListener(cvh.uiExecutor, stateCallback)
192     }
193 
194     fun getTaskViewBounds(): Rect {
195         val wm = context.getSystemService(WindowManager::class.java)
196         val windowMetrics = wm.getCurrentWindowMetrics()
197         val rect = windowMetrics.bounds
198         val metricInsets = windowMetrics.windowInsets
199         val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars()
200                 or Type.displayCutout())
201         val headerHeight = context.resources.getDimensionPixelSize(
202                 R.dimen.controls_detail_dialog_header_height)
203 
204         val finalRect = Rect(rect.left - insets.left /* left */,
205                 rect.top + insets.top + headerHeight /* top */,
206                 rect.right - insets.right /* right */,
207                 rect.bottom - insets.bottom /* bottom */)
208         return finalRect
209     }
210 
211     override fun dismiss() {
212         if (!isShowing()) return
213         taskView.release()
214 
215         super.dismiss()
216     }
217 }
218