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