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