1 /* 2 * Copyright (C) 2022 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 package com.android.systemui.screenrecord 17 18 import android.app.Activity 19 import android.app.PendingIntent 20 import android.content.Context 21 import android.content.Intent 22 import android.os.Bundle 23 import android.os.Handler 24 import android.os.Looper 25 import android.os.ResultReceiver 26 import android.os.UserHandle 27 import android.view.View 28 import android.view.View.GONE 29 import android.view.View.VISIBLE 30 import android.widget.AdapterView 31 import android.widget.ArrayAdapter 32 import android.widget.Spinner 33 import android.widget.Switch 34 import androidx.annotation.LayoutRes 35 import com.android.systemui.R 36 import com.android.systemui.animation.DialogLaunchAnimator 37 import com.android.systemui.media.MediaProjectionAppSelectorActivity 38 import com.android.systemui.media.MediaProjectionCaptureTarget 39 import com.android.systemui.plugins.ActivityStarter 40 import com.android.systemui.settings.UserContextProvider 41 42 /** Dialog to select screen recording options */ 43 class ScreenRecordPermissionDialog( 44 context: Context, 45 private val hostUserHandle: UserHandle, 46 private val controller: RecordingController, 47 private val activityStarter: ActivityStarter, 48 private val dialogLaunchAnimator: DialogLaunchAnimator, 49 private val userContextProvider: UserContextProvider, 50 private val onStartRecordingClicked: Runnable? 51 ) : 52 BaseScreenSharePermissionDialog( 53 context, 54 createOptionList(), 55 appName = null, 56 R.drawable.ic_screenrecord, 57 R.color.screenrecord_icon_color 58 ) { 59 private lateinit var tapsSwitch: Switch 60 private lateinit var tapsView: View 61 private lateinit var audioSwitch: Switch 62 private lateinit var options: Spinner 63 override fun onCreate(savedInstanceState: Bundle?) { 64 super.onCreate(savedInstanceState) 65 setDialogTitle(R.string.screenrecord_permission_dialog_title) 66 setTitle(R.string.screenrecord_title) 67 setStartButtonText(R.string.screenrecord_permission_dialog_continue) 68 setStartButtonOnClickListener { v: View? -> 69 onStartRecordingClicked?.run() 70 if (selectedScreenShareOption.mode == ENTIRE_SCREEN) { 71 requestScreenCapture(/* captureTarget= */ null) 72 } 73 if (selectedScreenShareOption.mode == SINGLE_APP) { 74 val intent = Intent(context, MediaProjectionAppSelectorActivity::class.java) 75 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 76 77 // We can't start activity for result here so we use result receiver to get 78 // the selected target to capture 79 intent.putExtra( 80 MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER, 81 CaptureTargetResultReceiver() 82 ) 83 84 intent.putExtra( 85 MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE, 86 hostUserHandle 87 ) 88 89 val animationController = dialogLaunchAnimator.createActivityLaunchController(v!!) 90 if (animationController == null) { 91 dismiss() 92 } 93 activityStarter.startActivity(intent, /* dismissShade= */ true, animationController) 94 } 95 dismiss() 96 } 97 setCancelButtonOnClickListener { dismiss() } 98 initRecordOptionsView() 99 } 100 101 @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options 102 103 private fun initRecordOptionsView() { 104 audioSwitch = requireViewById(R.id.screenrecord_audio_switch) 105 tapsSwitch = requireViewById(R.id.screenrecord_taps_switch) 106 tapsView = requireViewById(R.id.show_taps) 107 updateTapsViewVisibility() 108 options = requireViewById(R.id.screen_recording_options) 109 val a: ArrayAdapter<*> = 110 ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES) 111 a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) 112 options.adapter = a 113 options.setOnItemClickListenerInt { _: AdapterView<*>?, _: View?, _: Int, _: Long -> 114 audioSwitch.isChecked = true 115 } 116 } 117 118 override fun onItemSelected(adapterView: AdapterView<*>?, view: View, pos: Int, id: Long) { 119 super.onItemSelected(adapterView, view, pos, id) 120 updateTapsViewVisibility() 121 } 122 123 private fun updateTapsViewVisibility() { 124 tapsView.visibility = if (selectedScreenShareOption.mode == SINGLE_APP) GONE else VISIBLE 125 } 126 127 /** 128 * Starts screen capture after some countdown 129 * 130 * @param captureTarget target to capture (could be e.g. a task) or null to record the whole 131 * screen 132 */ 133 private fun requestScreenCapture(captureTarget: MediaProjectionCaptureTarget?) { 134 val userContext = userContextProvider.userContext 135 val showTaps = selectedScreenShareOption.mode != SINGLE_APP && tapsSwitch.isChecked 136 val audioMode = 137 if (audioSwitch.isChecked) options.selectedItem as ScreenRecordingAudioSource 138 else ScreenRecordingAudioSource.NONE 139 val startIntent = 140 PendingIntent.getForegroundService( 141 userContext, 142 RecordingService.REQUEST_CODE, 143 RecordingService.getStartIntent( 144 userContext, 145 Activity.RESULT_OK, 146 audioMode.ordinal, 147 showTaps, 148 captureTarget 149 ), 150 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 151 ) 152 val stopIntent = 153 PendingIntent.getService( 154 userContext, 155 RecordingService.REQUEST_CODE, 156 RecordingService.getStopIntent(userContext), 157 PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 158 ) 159 controller.startCountdown(DELAY_MS, INTERVAL_MS, startIntent, stopIntent) 160 } 161 162 private inner class CaptureTargetResultReceiver() : 163 ResultReceiver(Handler(Looper.getMainLooper())) { 164 override fun onReceiveResult(resultCode: Int, resultData: Bundle) { 165 if (resultCode == Activity.RESULT_OK) { 166 val captureTarget = 167 resultData.getParcelable( 168 MediaProjectionAppSelectorActivity.KEY_CAPTURE_TARGET, 169 MediaProjectionCaptureTarget::class.java 170 ) 171 172 // Start recording of the selected target 173 requestScreenCapture(captureTarget) 174 } 175 } 176 } 177 178 companion object { 179 private val MODES = 180 listOf( 181 ScreenRecordingAudioSource.INTERNAL, 182 ScreenRecordingAudioSource.MIC, 183 ScreenRecordingAudioSource.MIC_AND_INTERNAL 184 ) 185 private const val DELAY_MS: Long = 3000 186 private const val INTERVAL_MS: Long = 1000 187 private fun createOptionList(): List<ScreenShareOption> { 188 return listOf( 189 ScreenShareOption( 190 ENTIRE_SCREEN, 191 R.string.screen_share_permission_dialog_option_entire_screen, 192 R.string.screenrecord_permission_dialog_warning_entire_screen 193 ), 194 ScreenShareOption( 195 SINGLE_APP, 196 R.string.screen_share_permission_dialog_option_single_app, 197 R.string.screenrecord_permission_dialog_warning_single_app 198 ) 199 ) 200 } 201 } 202 } 203