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.recordissue
18 
19 import android.annotation.SuppressLint
20 import android.app.AlertDialog.BUTTON_POSITIVE
21 import android.content.Context
22 import android.content.Intent
23 import android.content.res.ColorStateList
24 import android.graphics.Color
25 import android.os.Bundle
26 import android.os.UserHandle
27 import android.view.Gravity
28 import android.view.LayoutInflater
29 import android.view.WindowManager
30 import android.widget.Button
31 import android.widget.PopupMenu
32 import android.widget.Switch
33 import androidx.annotation.MainThread
34 import androidx.annotation.WorkerThread
35 import com.android.systemui.dagger.qualifiers.Background
36 import com.android.systemui.dagger.qualifiers.Main
37 import com.android.systemui.flags.FeatureFlagsClassic
38 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
39 import com.android.systemui.mediaprojection.SessionCreationSource
40 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
41 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
42 import com.android.systemui.recordissue.IssueRecordingState.Companion.ALL_ISSUE_TYPES
43 import com.android.systemui.recordissue.IssueRecordingState.Companion.ISSUE_TYPE_NOT_SET
44 import com.android.systemui.res.R
45 import com.android.systemui.settings.UserTracker
46 import com.android.systemui.statusbar.phone.SystemUIDialog
47 import dagger.assisted.Assisted
48 import dagger.assisted.AssistedFactory
49 import dagger.assisted.AssistedInject
50 import java.util.concurrent.Executor
51 
52 private const val EXTRA_ISSUE_TYPE_RES = "extra_issueTypeRes"
53 
54 class RecordIssueDialogDelegate
55 @AssistedInject
56 constructor(
57     private val factory: SystemUIDialog.Factory,
58     private val userTracker: UserTracker,
59     private val flags: FeatureFlagsClassic,
60     @Background private val bgExecutor: Executor,
61     @Main private val mainExecutor: Executor,
62     private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
63     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
64     private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
65     private val state: IssueRecordingState,
66     @Assisted private val onStarted: Runnable,
67 ) : SystemUIDialog.Delegate {
68 
69     /** To inject dependencies and allow for easier testing */
70     @AssistedFactory
71     interface Factory {
72         /** Create a dialog object */
73         fun create(onStarted: Runnable): RecordIssueDialogDelegate
74     }
75 
76     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
77     private lateinit var issueTypeButton: Button
78 
79     @MainThread
80     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
81         dialog.apply {
82             setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
83             setTitle(context.getString(R.string.qs_record_issue_label))
84             setIcon(R.drawable.qs_record_issue_icon_off)
85             setNegativeButton(R.string.cancel) { _, _ -> }
86             setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
87         }
88     }
89 
90     override fun createDialog(): SystemUIDialog = factory.create(this)
91 
92     @MainThread
93     override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
94         dialog.apply {
95             window?.apply {
96                 addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
97                 setGravity(Gravity.CENTER)
98             }
99 
100             screenRecordSwitch =
101                 requireViewById<Switch>(R.id.screenrecord_switch).apply {
102                     isChecked = state.recordScreen
103                     setOnCheckedChangeListener { _, isChecked ->
104                         state.recordScreen = isChecked
105                         if (isChecked) {
106                             bgExecutor.execute { onScreenRecordSwitchClicked() }
107                         }
108                     }
109                 }
110 
111             requireViewById<Switch>(R.id.bugreport_switch).apply {
112                 isChecked = state.takeBugreport
113                 setOnCheckedChangeListener { _, isChecked -> state.takeBugreport = isChecked }
114             }
115 
116             issueTypeButton =
117                 requireViewById<Button>(R.id.issue_type_button).apply {
118                     val startButton = dialog.getButton(BUTTON_POSITIVE)
119                     if (state.issueTypeRes != ISSUE_TYPE_NOT_SET) {
120                         setText(state.issueTypeRes)
121                     } else {
122                         startButton.isEnabled = false
123                     }
124                     setOnClickListener {
125                         onIssueTypeClicked(context) { startButton.isEnabled = true }
126                     }
127                 }
128         }
129     }
130 
131     @WorkerThread
132     private fun onScreenRecordSwitchClicked() {
133         if (
134             devicePolicyResolver
135                 .get()
136                 .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
137         ) {
138             mainExecutor.execute {
139                 screenCaptureDisabledDialogDelegate.createSysUIDialog().show()
140                 screenRecordSwitch.isChecked = false
141             }
142             return
143         }
144 
145         mediaProjectionMetricsLogger.notifyProjectionInitiated(
146             userTracker.userId,
147             SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER,
148         )
149 
150         if (!state.hasUserApprovedScreenRecording) {
151             mainExecutor.execute {
152                 ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
153                     setOnCancelListener { screenRecordSwitch.isChecked = false }
154                     show()
155                 }
156             }
157         }
158     }
159 
160     @MainThread
161     private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) {
162         val popupMenu = PopupMenu(context, issueTypeButton)
163         val onMenuItemClickListener =
164             PopupMenu.OnMenuItemClickListener {
165                 issueTypeButton.text = it.title
166                 state.issueTypeRes =
167                     it.intent?.getIntExtra(EXTRA_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
168                         ?: ISSUE_TYPE_NOT_SET
169                 onIssueTypeSelected.run()
170                 true
171             }
172         ALL_ISSUE_TYPES.keys.forEach {
173             popupMenu.menu.add(it).apply {
174                 // Set this for every item in the list to ensure equal spacing. Set it to
175                 // transparent for non-selected items so icon is only visible for selected element.
176                 setIcon(R.drawable.arrow_pointing_down)
177                 if (it != state.issueTypeRes) {
178                     iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
179                 } else {
180                     contentDescription =
181                         context.getString(com.android.internal.R.string.selected) +
182                             " " +
183                             context.getString(it)
184                 }
185                 intent = Intent().putExtra(EXTRA_ISSUE_TYPE_RES, it)
186 
187                 if (it == R.string.custom) {
188                     setOnMenuItemClickListener {
189                         CustomTraceSettingsDialogDelegate(
190                                 factory,
191                                 state.customTraceState,
192                                 state.tagTitles,
193                             ) {
194                                 onMenuItemClickListener.onMenuItemClick(it)
195                             }
196                             .createDialog()
197                             .show()
198                         true
199                     }
200                 }
201             }
202         }
203         popupMenu.apply {
204             setOnMenuItemClickListener(onMenuItemClickListener)
205             setForceShowIcon(true)
206             show()
207         }
208     }
209 }
210