1 /*
2  * Copyright (C) 2024 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.screenrecord.data.repository
18 
19 import com.android.systemui.dagger.SysUISingleton
20 import com.android.systemui.dagger.qualifiers.Background
21 import com.android.systemui.screenrecord.RecordingController
22 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
23 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
24 import javax.inject.Inject
25 import kotlin.coroutines.CoroutineContext
26 import kotlinx.coroutines.channels.awaitClose
27 import kotlinx.coroutines.flow.Flow
28 import kotlinx.coroutines.flow.distinctUntilChanged
29 import kotlinx.coroutines.flow.flowOn
30 import kotlinx.coroutines.flow.onStart
31 import kotlinx.coroutines.withContext
32 
33 /**
34  * Repository storing information about the state of screen recording.
35  *
36  * Mostly a wrapper around [RecordingController] so that new screen-recording-related code can use
37  * recommended architecture.
38  */
39 interface ScreenRecordRepository {
40     /** The current screen recording state. Note that this is a cold flow. */
41     val screenRecordState: Flow<ScreenRecordModel>
42 
43     /** Stops the recording. */
stopRecordingnull44     suspend fun stopRecording()
45 }
46 
47 @SysUISingleton
48 class ScreenRecordRepositoryImpl
49 @Inject
50 constructor(
51     @Background private val bgCoroutineContext: CoroutineContext,
52     private val recordingController: RecordingController,
53 ) : ScreenRecordRepository {
54 
55     override val screenRecordState: Flow<ScreenRecordModel> =
56         conflatedCallbackFlow {
57                 val callback =
58                     object : RecordingController.RecordingStateChangeCallback {
59                         override fun onRecordingStart() {
60                             trySend(ScreenRecordModel.Recording)
61                         }
62 
63                         override fun onRecordingEnd() {
64                             trySend(ScreenRecordModel.DoingNothing)
65                         }
66 
67                         override fun onCountdown(millisUntilFinished: Long) {
68                             trySend(ScreenRecordModel.Starting(millisUntilFinished))
69                         }
70 
71                         override fun onCountdownEnd() {
72                             if (
73                                 !recordingController.isRecording && !recordingController.isStarting
74                             ) {
75                                 // The recording was in Starting state but got canceled before
76                                 // actually starting
77                                 trySend(ScreenRecordModel.DoingNothing)
78                             }
79                         }
80                     }
81                 recordingController.addCallback(callback)
82                 awaitClose { recordingController.removeCallback(callback) }
83             }
84             .onStart { emit(generateModel()) }
85             .distinctUntilChanged()
86             .flowOn(bgCoroutineContext)
87 
88     private fun generateModel(): ScreenRecordModel {
89         return if (recordingController.isRecording) {
90             ScreenRecordModel.Recording
91         } else if (recordingController.isStarting) {
92             ScreenRecordModel.Starting(0)
93         } else {
94             ScreenRecordModel.DoingNothing
95         }
96     }
97 
98     override suspend fun stopRecording() {
99         withContext(bgCoroutineContext) { recordingController.stopRecording() }
100     }
101 }
102