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.qs.tiles.impl.screenrecord.domain.interactor
18 
19 import android.content.Context
20 import android.util.Log
21 import com.android.internal.jank.InteractionJankMonitor
22 import com.android.systemui.animation.DialogCuj
23 import com.android.systemui.animation.DialogTransitionAnimator
24 import com.android.systemui.animation.Expandable
25 import com.android.systemui.dagger.qualifiers.Application
26 import com.android.systemui.dagger.qualifiers.Background
27 import com.android.systemui.dagger.qualifiers.Main
28 import com.android.systemui.flags.FeatureFlagsClassic
29 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
30 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
31 import com.android.systemui.plugins.ActivityStarter
32 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
33 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
34 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
35 import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
36 import com.android.systemui.screenrecord.RecordingController
37 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
38 import com.android.systemui.screenrecord.data.repository.ScreenRecordRepository
39 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
40 import javax.inject.Inject
41 import kotlin.coroutines.CoroutineContext
42 import kotlinx.coroutines.withContext
43 
44 /** Handles screen recorder tile clicks. */
45 class ScreenRecordTileUserActionInteractor
46 @Inject
47 constructor(
48     @Application private val context: Context,
49     @Main private val mainContext: CoroutineContext,
50     @Background private val backgroundContext: CoroutineContext,
51     private val screenRecordRepository: ScreenRecordRepository,
52     private val recordingController: RecordingController,
53     private val keyguardInteractor: KeyguardInteractor,
54     private val keyguardDismissUtil: KeyguardDismissUtil,
55     private val dialogTransitionAnimator: DialogTransitionAnimator,
56     private val panelInteractor: PanelInteractor,
57     private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
58     private val featureFlags: FeatureFlagsClassic,
59     private val activityStarter: ActivityStarter,
60 ) : QSTileUserActionInteractor<ScreenRecordModel> {
handleInputnull61     override suspend fun handleInput(input: QSTileInput<ScreenRecordModel>): Unit =
62         with(input) {
63             when (action) {
64                 is QSTileUserAction.Click -> {
65                     when (data) {
66                         is ScreenRecordModel.Starting -> {
67                             Log.d(TAG, "Cancelling countdown")
68                             withContext(backgroundContext) { recordingController.cancelCountdown() }
69                         }
70                         is ScreenRecordModel.Recording -> screenRecordRepository.stopRecording()
71                         is ScreenRecordModel.DoingNothing ->
72                             withContext(mainContext) {
73                                 showPrompt(action.expandable, user.identifier)
74                             }
75                     }
76                 }
77                 is QSTileUserAction.LongClick -> {} // no-op
78             }
79         }
80 
showPromptnull81     private fun showPrompt(expandable: Expandable?, userId: Int) {
82         // Create the recording dialog that will collapse the shade only if we start the recording.
83         val onStartRecordingClicked = Runnable {
84             // We dismiss the shade. Since starting the recording will also dismiss the dialog, we
85             // disable the exit animation which looks weird when it happens at the same time as the
86             // shade collapsing.
87             dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
88             panelInteractor.collapsePanels()
89         }
90 
91         val dialog =
92             recordingController.createScreenRecordDialog(
93                 context,
94                 featureFlags,
95                 dialogTransitionAnimator,
96                 activityStarter,
97                 onStartRecordingClicked
98             )
99 
100         if (dialog == null) {
101             Log.w(TAG, "showPrompt: dialog was null")
102             return
103         }
104 
105         // We animate from the touched expandable only if we are not on the keyguard, given that if
106         // we
107         // are we will dismiss it which will also collapse the shade.
108         val shouldAnimateFromExpandable =
109             expandable != null && !keyguardInteractor.isKeyguardShowing()
110         val dismissAction =
111             ActivityStarter.OnDismissAction {
112                 if (shouldAnimateFromExpandable) {
113                     val controller =
114                         expandable?.dialogTransitionController(
115                             DialogCuj(
116                                 InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
117                                 INTERACTION_JANK_TAG
118                             )
119                         )
120                     controller?.let {
121                         dialogTransitionAnimator.show(
122                             dialog,
123                             controller,
124                             animateBackgroundBoundsChange = true,
125                         )
126                     } ?: dialog.show()
127                 } else {
128                     dialog.show()
129                 }
130                 mediaProjectionMetricsLogger.notifyPermissionRequestDisplayed(userId)
131                 false
132             }
133 
134         keyguardDismissUtil.executeWhenUnlocked(
135             dismissAction,
136             false /* requiresShadeOpen */,
137             true /* afterKeyguardDone */
138         )
139     }
140 
141     private companion object {
142         const val TAG = "ScreenRecordTileUserActionInteractor"
143         const val INTERACTION_JANK_TAG = "screen_record"
144     }
145 }
146