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