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.recordissue 18 19 import android.app.IActivityManager 20 import android.app.NotificationManager 21 import android.content.Context 22 import android.content.Intent 23 import android.content.res.Resources 24 import android.net.Uri 25 import android.os.Handler 26 import android.os.IBinder 27 import android.os.UserHandle 28 import android.util.Log 29 import com.android.internal.logging.UiEventLogger 30 import com.android.systemui.animation.DialogTransitionAnimator 31 import com.android.systemui.dagger.qualifiers.LongRunning 32 import com.android.systemui.dagger.qualifiers.Main 33 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor 34 import com.android.systemui.res.R 35 import com.android.systemui.screenrecord.RecordingController 36 import com.android.systemui.screenrecord.RecordingService 37 import com.android.systemui.screenrecord.RecordingServiceStrings 38 import com.android.systemui.screenrecord.ScreenMediaRecorder.SavedRecording 39 import com.android.systemui.settings.UserContextProvider 40 import com.android.systemui.statusbar.phone.KeyguardDismissUtil 41 import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE 42 import com.android.traceur.PresetTraceConfigs 43 import com.android.traceur.TraceConfig 44 import java.util.concurrent.Executor 45 import javax.inject.Inject 46 47 class IssueRecordingService 48 @Inject 49 constructor( 50 controller: RecordingController, 51 @LongRunning private val bgExecutor: Executor, 52 @Main handler: Handler, 53 uiEventLogger: UiEventLogger, 54 notificationManager: NotificationManager, 55 userContextProvider: UserContextProvider, 56 keyguardDismissUtil: KeyguardDismissUtil, 57 dialogTransitionAnimator: DialogTransitionAnimator, 58 panelInteractor: PanelInteractor, 59 private val issueRecordingState: IssueRecordingState, 60 traceurConnectionProvider: TraceurConnection.Provider, 61 iActivityManager: IActivityManager, 62 screenRecordingStartTimeStore: ScreenRecordingStartTimeStore, 63 ) : 64 RecordingService( 65 controller, 66 bgExecutor, 67 handler, 68 uiEventLogger, 69 notificationManager, 70 userContextProvider, 71 keyguardDismissUtil, 72 screenRecordingStartTimeStore, 73 ) { 74 75 private val traceurConnection: TraceurConnection = traceurConnectionProvider.create() 76 77 private val session = 78 IssueRecordingServiceSession( 79 bgExecutor, 80 dialogTransitionAnimator, 81 panelInteractor, 82 traceurConnection, 83 issueRecordingState, 84 iActivityManager, 85 notificationManager, 86 userContextProvider, 87 screenRecordingStartTimeStore, 88 ) 89 90 /** 91 * It is necessary to bind to IssueRecordingService from the Record Issue Tile because there are 92 * instances where this service is not created in the same user profile as the record issue tile 93 * aka, headless system user mode. In those instances, the TraceurConnection will be considered 94 * a leak in between notification actions unless the tile is bound to this service to keep it 95 * alive. 96 */ onBindnull97 override fun onBind(intent: Intent): IBinder? { 98 traceurConnection.doBind() 99 return super.onBind(intent) 100 } 101 onUnbindnull102 override fun onUnbind(intent: Intent?): Boolean { 103 traceurConnection.doUnBind() 104 return super.onUnbind(intent) 105 } 106 getTagnull107 override fun getTag(): String = TAG 108 109 override fun getChannelId(): String = CHANNEL_ID 110 111 override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources) 112 113 override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { 114 Log.d(getTag(), "handling action: ${intent?.action}") 115 when (intent?.action) { 116 ACTION_START -> { 117 val screenRecord = intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false) 118 with(session) { 119 traceConfig = 120 intent.getParcelableExtra(INTENT_EXTRA_TRACE_TYPE, TraceConfig::class.java) 121 ?: PresetTraceConfigs.getDefaultConfig() 122 takeBugReport = intent.getBooleanExtra(EXTRA_BUG_REPORT, false) 123 this.screenRecord = screenRecord 124 start() 125 } 126 if (!screenRecord) { 127 // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action 128 // will circumvent the RecordingService's screen recording start code. 129 return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId) 130 } 131 } 132 ACTION_STOP, 133 ACTION_STOP_NOTIF -> session.stop() 134 ACTION_SHARE -> { 135 session.share( 136 intent.getIntExtra(EXTRA_NOTIFICATION_ID, mNotificationId), 137 intent.getParcelableExtra(EXTRA_PATH, Uri::class.java), 138 ) 139 // Unlike all other actions, action_share has different behavior for the screen 140 // recording qs tile than it does for the record issue qs tile. Return sticky to 141 // avoid running any of the base class' code for this action. 142 return START_STICKY 143 } 144 else -> {} 145 } 146 return super.onStartCommand(intent, flags, startId) 147 } 148 149 /** 150 * If the user chooses to create a bugreport, we do not want to make them click share twice. To 151 * avoid that, the code immediately triggers the bugreport flow which will handle the rest. 152 */ onRecordingSavednull153 override fun onRecordingSaved(recording: SavedRecording?, currentUser: UserHandle) { 154 if (session.takeBugReport) { 155 session.share(mNotificationId, recording?.uri) 156 } else { 157 super.onRecordingSaved(recording, currentUser) 158 } 159 } 160 161 companion object { 162 private const val TAG = "IssueRecordingService" 163 private const val CHANNEL_ID = "issue_record" 164 const val EXTRA_SCREEN_RECORD = "extra_screenRecord" 165 const val EXTRA_BUG_REPORT = "extra_bugReport" 166 167 /** 168 * Get an intent to stop the issue recording service. 169 * 170 * @param context Context from the requesting activity 171 * @return 172 */ getStopIntentnull173 fun getStopIntent(context: Context): Intent = 174 Intent(context, IssueRecordingService::class.java) 175 .setAction(ACTION_STOP) 176 .putExtra(Intent.EXTRA_USER_HANDLE, context.userId) 177 178 /** 179 * Get an intent to start the issue recording service. 180 * 181 * @param context Context from the requesting activity 182 */ 183 fun getStartIntent( 184 context: Context, 185 traceConfig: TraceConfig, 186 screenRecord: Boolean, 187 bugReport: Boolean, 188 ): Intent = 189 Intent(context, IssueRecordingService::class.java) 190 .setAction(ACTION_START) 191 .putExtra(INTENT_EXTRA_TRACE_TYPE, traceConfig) 192 .putExtra(EXTRA_SCREEN_RECORD, screenRecord) 193 .putExtra(EXTRA_BUG_REPORT, bugReport) 194 } 195 } 196 197 private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) { 198 override val title 199 get() = res.getString(R.string.issuerecord_title) 200 201 override val notificationChannelDescription 202 get() = res.getString(R.string.issuerecord_channel_description) 203 204 override val startErrorResId 205 get() = R.string.issuerecord_start_error 206 207 override val startError 208 get() = res.getString(R.string.issuerecord_start_error) 209 210 override val saveErrorResId 211 get() = R.string.issuerecord_save_error 212 213 override val saveError 214 get() = res.getString(R.string.issuerecord_save_error) 215 216 override val ongoingRecording 217 get() = res.getString(R.string.issuerecord_ongoing_screen_only) 218 219 override val backgroundProcessingLabel 220 get() = res.getString(R.string.issuerecord_background_processing_label) 221 222 override val saveTitle 223 get() = res.getString(R.string.issuerecord_save_title) 224 } 225