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