1 /*
<lambda>null2  * Copyright 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 
17 package androidx.camera.integration.avsync
18 
19 import android.content.Context
20 import android.content.Context.AUDIO_SERVICE
21 import android.media.AudioManager
22 import androidx.camera.core.Logger
23 import androidx.camera.integration.avsync.model.AudioGenerator
24 import androidx.camera.integration.avsync.model.CameraHelper
25 import androidx.camera.integration.avsync.model.CameraHelper.Companion.CameraImplementation
26 import androidx.compose.runtime.getValue
27 import androidx.compose.runtime.mutableStateOf
28 import androidx.compose.runtime.setValue
29 import androidx.core.util.Preconditions
30 import androidx.lifecycle.LifecycleOwner
31 import androidx.lifecycle.ViewModel
32 import androidx.lifecycle.viewModelScope
33 import kotlinx.coroutines.Dispatchers
34 import kotlinx.coroutines.Job
35 import kotlinx.coroutines.delay
36 import kotlinx.coroutines.flow.flow
37 import kotlinx.coroutines.flow.launchIn
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.onCompletion
40 import kotlinx.coroutines.withContext
41 
42 private const val ACTIVE_LENGTH_SEC: Double = 0.5
43 private const val ACTIVE_INTERVAL_SEC: Double = 1.0
44 private const val ACTIVE_DELAY_SEC: Double = 0.0
45 private const val VOLUME_PERCENTAGE: Double = 1.0
46 private const val TAG = "SignalGeneratorViewModel"
47 
48 enum class ActivationSignal {
49     Active,
50     Inactive
51 }
52 
53 class SignalGeneratorViewModel : ViewModel() {
54 
55     private var signalGenerationJob: Job? = null
56     private lateinit var audioGenerator: AudioGenerator
57     private lateinit var cameraHelper: CameraHelper
58     private lateinit var audioManager: AudioManager
59     private var originalVolume: Int = 0
60 
61     var isGeneratorReady: Boolean by mutableStateOf(false)
62         private set
63 
64     var isRecorderReady: Boolean by mutableStateOf(false)
65         private set
66 
67     var isSignalGenerating: Boolean by mutableStateOf(false)
68         private set
69 
70     var isActivePeriod: Boolean by mutableStateOf(false)
71         private set
72 
73     var isRecording: Boolean by mutableStateOf(false)
74         private set
75 
76     var isPaused: Boolean by mutableStateOf(false)
77         private set
78 
initialRecordernull79     suspend fun initialRecorder(
80         context: Context,
81         lifecycleOwner: LifecycleOwner,
82         cameraImplementation: CameraImplementation
83     ) {
84         cameraHelper = CameraHelper(cameraImplementation)
85         withContext(Dispatchers.Main) {
86             isRecorderReady = cameraHelper.bindCamera(context, lifecycleOwner)
87         }
88     }
89 
initialSignalGeneratornull90     suspend fun initialSignalGenerator(context: Context, beepFrequency: Int, beepEnabled: Boolean) {
91         audioManager = context.getSystemService(AUDIO_SERVICE) as AudioManager
92         saveOriginalVolume()
93 
94         withContext(Dispatchers.Default) {
95             audioGenerator = AudioGenerator(beepEnabled)
96             audioGenerator.initial(
97                 context = context,
98                 frequency = beepFrequency,
99                 beepLengthInSec = ACTIVE_LENGTH_SEC,
100             )
101             isGeneratorReady = true
102         }
103     }
104 
setVolumenull105     private fun setVolume(percentage: Double = VOLUME_PERCENTAGE) {
106         Preconditions.checkArgument(percentage in 0.0..1.0)
107 
108         val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
109         val targetVolume = (maxVolume * percentage).toInt()
110         audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0)
111     }
112 
startSignalGenerationnull113     fun startSignalGeneration() {
114         Logger.d(TAG, "Start signal generation.")
115         Preconditions.checkState(isGeneratorReady)
116 
117         setVolume()
118 
119         signalGenerationJob?.cancel()
120         isSignalGenerating = true
121         signalGenerationJob =
122             activationSignalFlow()
123                 .map { activationSignal ->
124                     when (activationSignal) {
125                         ActivationSignal.Active -> {
126                             isActivePeriod = true
127                             playBeepSound()
128                         }
129                         ActivationSignal.Inactive -> {
130                             isActivePeriod = false
131                             stopBeepSound()
132                         }
133                     }
134                 }
135                 .onCompletion {
136                     stopBeepSound()
137                     restoreOriginalVolume()
138                     isActivePeriod = false
139                 }
140                 .launchIn(viewModelScope)
141     }
142 
stopSignalGenerationnull143     fun stopSignalGeneration() {
144         Logger.d(TAG, "Stop signal generation.")
145         Preconditions.checkState(isGeneratorReady)
146 
147         isSignalGenerating = false
148         signalGenerationJob?.cancel()
149         signalGenerationJob = null
150     }
151 
startRecordingnull152     fun startRecording(context: Context) {
153         Logger.d(TAG, "Start recording.")
154         Preconditions.checkState(isRecorderReady)
155 
156         cameraHelper.startRecording(context)
157         isRecording = true
158     }
159 
stopRecordingnull160     fun stopRecording() {
161         Logger.d(TAG, "Stop recording.")
162         Preconditions.checkState(isRecorderReady)
163 
164         cameraHelper.stopRecording()
165         isRecording = false
166         isPaused = false
167     }
168 
pauseRecordingnull169     fun pauseRecording() {
170         Logger.d(TAG, "Pause recording.")
171         Preconditions.checkState(isRecorderReady)
172 
173         cameraHelper.pauseRecording()
174         isPaused = true
175     }
176 
resumeRecordingnull177     fun resumeRecording() {
178         Logger.d(TAG, "Resume recording.")
179         Preconditions.checkState(isRecorderReady)
180 
181         cameraHelper.resumeRecording()
182         isPaused = false
183     }
184 
saveOriginalVolumenull185     private fun saveOriginalVolume() {
186         originalVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
187     }
188 
restoreOriginalVolumenull189     private fun restoreOriginalVolume() {
190         audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0)
191     }
192 
<lambda>null193     private fun activationSignalFlow() = flow {
194         delay((ACTIVE_DELAY_SEC * 1000).toLong())
195         while (true) {
196             emit(ActivationSignal.Active)
197             delay((ACTIVE_LENGTH_SEC * 1000).toLong())
198             emit(ActivationSignal.Inactive)
199             delay((ACTIVE_INTERVAL_SEC * 1000).toLong())
200         }
201     }
202 
playBeepSoundnull203     private fun playBeepSound() {
204         audioGenerator.start()
205     }
206 
stopBeepSoundnull207     private fun stopBeepSound() {
208         audioGenerator.stop()
209     }
210 }
211