• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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