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