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