diff options
12 files changed, 401 insertions, 222 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt index 3e5dee69c85c..8e57914050d6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/IssueRecordingServiceSessionTest.kt @@ -53,7 +53,7 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() { private val bgExecutor = kosmos.fakeExecutor private val userContextProvider: UserContextProvider = kosmos.userTracker private val dialogTransitionAnimator: DialogTransitionAnimator = kosmos.dialogTransitionAnimator - private lateinit var traceurMessageSender: TraceurMessageSender + private lateinit var traceurConnection: TraceurConnection private val issueRecordingState = IssueRecordingState(kosmos.userTracker, kosmos.userFileManager) @@ -65,13 +65,13 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() { @Before fun setup() { - traceurMessageSender = mock<TraceurMessageSender>() + traceurConnection = mock<TraceurConnection>() underTest = IssueRecordingServiceSession( bgExecutor, dialogTransitionAnimator, panelInteractor, - traceurMessageSender, + traceurConnection, issueRecordingState, iActivityManager, notificationManager, @@ -85,7 +85,7 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() { bgExecutor.runAllReady() Truth.assertThat(issueRecordingState.isRecording).isTrue() - verify(traceurMessageSender).startTracing(any<TraceConfig>()) + verify(traceurConnection).startTracing(any<TraceConfig>()) } @Test @@ -94,7 +94,7 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() { bgExecutor.runAllReady() Truth.assertThat(issueRecordingState.isRecording).isFalse() - verify(traceurMessageSender).stopTracing() + verify(traceurConnection).stopTracing() } @Test @@ -124,7 +124,7 @@ class IssueRecordingServiceSessionTest : SysuiTestCase() { underTest.share(0, uri, mContext) bgExecutor.runAllReady() - verify(traceurMessageSender).shareTraces(mContext, uri) + verify(traceurConnection).shareTraces(mContext, uri) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index 8d84c3e7392e..9ca8f447d374 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -78,7 +78,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { @Mock private lateinit var sysuiState: SysUiState @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock private lateinit var traceurMessageSender: TraceurMessageSender + @Mock private lateinit var traceurConnection: TraceurConnection private val systemClock = FakeSystemClock() private val bgExecutor = FakeExecutor(systemClock) private val mainExecutor = FakeExecutor(systemClock) @@ -104,7 +104,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { systemUIDialogManager, sysuiState, broadcastDispatcher, - mDialogTransitionAnimator + mDialogTransitionAnimator, ) ) @@ -120,7 +120,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { mediaProjectionMetricsLogger, screenCaptureDisabledDialogDelegate, state, - traceurMessageSender + traceurConnection, ) { latch.countDown() } @@ -166,7 +166,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { verify(mediaProjectionMetricsLogger, never()) .notifyProjectionInitiated( anyInt(), - eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER), ) assertThat(screenRecordSwitch.isChecked).isFalse() } @@ -188,7 +188,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { verify(mediaProjectionMetricsLogger) .notifyProjectionInitiated( anyInt(), - eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER), ) verify(factory, times(2)).create(any(SystemUIDialog.Delegate::class.java)) } @@ -208,7 +208,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { verify(mediaProjectionMetricsLogger) .notifyProjectionInitiated( anyInt(), - eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER) + eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER), ) verify(factory, never()).create() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt new file mode 100644 index 000000000000..85a8237ff955 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/TraceurConnectionTest.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.os.IBinder +import android.os.Looper +import android.os.Messenger +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserContextProvider +import com.android.traceur.PresetTraceConfigs +import java.util.concurrent.CountDownLatch +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class TraceurConnectionTest : SysuiTestCase() { + + @Mock private lateinit var userContextProvider: UserContextProvider + + private lateinit var underTest: TraceurConnection + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(userContextProvider.userContext).thenReturn(mContext) + underTest = TraceurConnection(userContextProvider, Looper.getMainLooper()) + } + + @Test + fun onBoundRunnables_areRun_whenServiceIsBound() { + val latch = CountDownLatch(1) + underTest.onBound.add { latch.countDown() } + + underTest.onServiceConnected( + InstrumentationRegistry.getInstrumentation().componentName, + mock(IBinder::class.java), + ) + + latch.await() + } + + @Test + fun startTracing_sendsMsg_toStartTracing() { + underTest.binder = mock(Messenger::class.java) + + underTest.startTracing(PresetTraceConfigs.getThermalConfig()) + + verify(underTest.binder)!!.send(any()) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt new file mode 100644 index 000000000000..f671bf44c42b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/recordissue/UserAwareConnectionTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.content.Context +import android.content.Intent +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.UserContextProvider +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class UserAwareConnectionTest : SysuiTestCase() { + + @Mock private lateinit var userContextProvider: UserContextProvider + @Mock private lateinit var mockContext: Context + + private lateinit var underTest: UserAwareConnection + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(userContextProvider.userContext).thenReturn(mockContext) + whenever(mockContext.bindService(any(), any(), anyInt())).thenReturn(true) + underTest = UserAwareConnection(userContextProvider, Intent()) + } + + @Test + fun doBindService_requestToBindToTheService_viaTheCorrectUserContext() { + underTest.doBind() + + verify(userContextProvider).userContext + } + + @Test + fun doBindService_DoesntRequestToBindToTheService_IfAlreadyRequested() { + underTest.doBind() + underTest.doBind() + underTest.doBind() + + verify(userContextProvider, times(1)).userContext + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index d89e73d2c69f..c316c8626e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -48,7 +48,7 @@ import com.android.systemui.recordissue.IssueRecordingService.Companion.getStopI import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC -import com.android.systemui.recordissue.TraceurMessageSender +import com.android.systemui.recordissue.TraceurConnection import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingController import com.android.systemui.screenrecord.RecordingService @@ -78,7 +78,7 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val panelInteractor: PanelInteractor, private val userContextProvider: UserContextProvider, - private val traceurMessageSender: TraceurMessageSender, + private val traceurConnection: TraceurConnection, @Background private val bgExecutor: Executor, private val issueRecordingState: IssueRecordingState, private val delegateFactory: RecordIssueDialogDelegate.Factory, @@ -93,7 +93,7 @@ constructor( metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, ) { private val onRecordingChangeListener = Runnable { refreshState() } @@ -109,7 +109,7 @@ constructor( override fun handleDestroy() { super.handleDestroy() - bgExecutor.execute { traceurMessageSender.unbindFromTraceur(mContext) } + bgExecutor.execute { traceurConnection.doUnBind() } } override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label) @@ -142,7 +142,7 @@ constructor( DELAY_MS, INTERVAL_MS, pendingServiceIntent(getStartIntent(userContextProvider.userContext)), - pendingServiceIntent(getStopIntent(userContextProvider.userContext)) + pendingServiceIntent(getStopIntent(userContextProvider.userContext)), ) private fun stopIssueRecordingService() = @@ -154,7 +154,7 @@ constructor( userContextProvider.userContext, RecordingService.REQUEST_CODE, action, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, ) private fun showPrompt(expandable: Expandable?) { diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 4d2bc91aa52a..e804baffce05 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -50,7 +50,7 @@ constructor( keyguardDismissUtil: KeyguardDismissUtil, dialogTransitionAnimator: DialogTransitionAnimator, panelInteractor: PanelInteractor, - traceurMessageSender: TraceurMessageSender, + traceurConnection: TraceurConnection, private val issueRecordingState: IssueRecordingState, iActivityManager: IActivityManager, ) : @@ -69,7 +69,7 @@ constructor( bgExecutor, dialogTransitionAnimator, panelInteractor, - traceurMessageSender, + traceurConnection, issueRecordingState, iActivityManager, notificationManager, diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt index e4d3e6cae502..6fc248ffa459 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingServiceSession.kt @@ -42,7 +42,7 @@ class IssueRecordingServiceSession( private val bgExecutor: Executor, private val dialogTransitionAnimator: DialogTransitionAnimator, private val panelInteractor: PanelInteractor, - private val traceurMessageSender: TraceurMessageSender, + private val traceurConnection: TraceurConnection, private val issueRecordingState: IssueRecordingState, private val iActivityManager: IActivityManager, private val notificationManager: NotificationManager, @@ -50,7 +50,7 @@ class IssueRecordingServiceSession( ) { fun start() { - bgExecutor.execute { traceurMessageSender.startTracing(issueRecordingState.traceConfig) } + bgExecutor.execute { traceurConnection.startTracing(issueRecordingState.traceConfig) } issueRecordingState.isRecording = true } @@ -59,7 +59,7 @@ class IssueRecordingServiceSession( if (issueRecordingState.traceConfig.longTrace) { Settings.Global.putInt(contentResolver, NOTIFY_SESSION_ENDED_SETTING, DISABLED) } - traceurMessageSender.stopTracing() + traceurConnection.stopTracing() } issueRecordingState.isRecording = false } @@ -75,7 +75,7 @@ class IssueRecordingServiceSession( if (issueRecordingState.takeBugreport) { iActivityManager.requestBugReportWithExtraAttachment(screenRecording) } else { - traceurMessageSender.shareTraces(context, screenRecording) + traceurConnection.shareTraces(context, screenRecording) } } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index ed67e64dfb76..e38fe9aafe32 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -64,7 +64,7 @@ constructor( private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate, private val state: IssueRecordingState, - private val traceurMessageSender: TraceurMessageSender, + private val traceurConnection: TraceurConnection, @Assisted private val onStarted: Runnable, ) : SystemUIDialog.Delegate { @@ -88,8 +88,8 @@ constructor( setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() } } bgExecutor.execute { - traceurMessageSender.onBoundToTraceur.add { traceurMessageSender.getTags(state) } - traceurMessageSender.bindToTraceur(dialog.context) + traceurConnection.onBound.add { traceurConnection.getTags(state) } + traceurConnection.doBind() } } @@ -151,7 +151,7 @@ constructor( mediaProjectionMetricsLogger.notifyProjectionInitiated( userTracker.userId, - SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER + SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER, ) if (!state.hasUserApprovedScreenRecording) { @@ -189,7 +189,7 @@ constructor( CustomTraceSettingsDialogDelegate( factory, state.customTraceState, - state.tagTitles + state.tagTitles, ) { onMenuItemClickListener.onMenuItemClick(it) } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt new file mode 100644 index 000000000000..75df49e3676f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurConnection.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.Message +import android.os.Messenger +import android.util.Log +import androidx.annotation.WorkerThread +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER +import com.android.systemui.settings.UserContextProvider +import com.android.traceur.FileSender +import com.android.traceur.MessageConstants +import com.android.traceur.MessageConstants.TRACING_APP_ACTIVITY +import com.android.traceur.MessageConstants.TRACING_APP_PACKAGE_NAME +import com.android.traceur.TraceConfig +import java.util.concurrent.CopyOnWriteArrayList +import javax.inject.Inject + +private const val TAG = "TraceurConnection" + +@SysUISingleton +class TraceurConnection +@Inject +constructor(userContextProvider: UserContextProvider, @Background private val bgLooper: Looper) : + UserAwareConnection( + userContextProvider, + Intent().setClassName(TRACING_APP_PACKAGE_NAME, TRACING_APP_ACTIVITY), + ) { + + val onBound: MutableList<Runnable> = CopyOnWriteArrayList(mutableListOf()) + + override fun onServiceConnected(className: ComponentName, service: IBinder) { + super.onServiceConnected(className, service) + onBound.forEach(Runnable::run) + onBound.clear() + } + + @WorkerThread + fun startTracing(traceType: TraceConfig) { + val data = + Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) } + sendMessage(MessageConstants.START_WHAT, data) + } + + @WorkerThread fun stopTracing() = sendMessage(MessageConstants.STOP_WHAT) + + @WorkerThread + fun shareTraces(context: Context, screenRecord: Uri?) { + val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, bgLooper)) + sendMessage(MessageConstants.SHARE_WHAT, replyTo = replyHandler) + } + + @WorkerThread + fun getTags(state: IssueRecordingState) = + sendMessage(MessageConstants.TAGS_WHAT, replyTo = Messenger(TagsHandler(bgLooper, state))) + + @WorkerThread + private fun sendMessage(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) = + try { + val msg = + Message.obtain().apply { + this.what = what + this.data = data + this.replyTo = replyTo + } + binder!!.send(msg) + } catch (e: Exception) { + Log.e(TAG, "failed to notify Traceur", e) + } +} + +private class ShareFilesHandler( + private val context: Context, + private val screenRecord: Uri?, + looper: Looper, +) : Handler(looper) { + + override fun handleMessage(msg: Message) { + if (MessageConstants.SHARE_WHAT == msg.what) { + shareTraces( + msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java), + msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java), + ) + } else { + throw IllegalArgumentException("received unknown msg.what: " + msg.what) + } + } + + private fun shareTraces(perfetto: Uri?, winscope: Uri?) { + val uris: ArrayList<Uri> = + ArrayList<Uri>().apply { + perfetto?.let { add(it) } + winscope?.let { add(it) } + screenRecord?.let { add(it) } + } + val fileSharingIntent = + FileSender.buildSendIntent(context, uris) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) + context.startActivity(fileSharingIntent) + } +} + +private class TagsHandler(looper: Looper, private val state: IssueRecordingState) : + Handler(looper) { + + override fun handleMessage(msg: Message) { + if (MessageConstants.TAGS_WHAT == msg.what) { + val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS) + val values = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS) + if (keys == null || values == null) { + throw IllegalArgumentException( + "Neither keys: $keys, nor values: $values can be null" + ) + } + state.tagTitles = + keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet() + } else { + throw IllegalArgumentException("received unknown msg.what: " + msg.what) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt deleted file mode 100644 index 8bfd14a68811..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.recordissue - -import android.annotation.SuppressLint -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.ServiceConnection -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.os.Handler -import android.os.IBinder -import android.os.Looper -import android.os.Message -import android.os.Messenger -import android.util.Log -import androidx.annotation.WorkerThread -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.recordissue.IssueRecordingState.Companion.TAG_TITLE_DELIMITER -import com.android.traceur.FileSender -import com.android.traceur.MessageConstants -import com.android.traceur.TraceConfig -import javax.inject.Inject - -private const val TAG = "TraceurMessageSender" - -@SysUISingleton -class TraceurMessageSender @Inject constructor(@Background private val backgroundLooper: Looper) { - private var binder: Messenger? = null - private var isBound: Boolean = false - - val onBoundToTraceur = mutableListOf<Runnable>() - - private val traceurConnection = - object : ServiceConnection { - override fun onServiceConnected(className: ComponentName, service: IBinder) { - binder = Messenger(service) - isBound = true - onBoundToTraceur.forEach(Runnable::run) - onBoundToTraceur.clear() - } - - override fun onServiceDisconnected(className: ComponentName) { - binder = null - isBound = false - } - } - - @SuppressLint("WrongConstant") - @WorkerThread - fun bindToTraceur(context: Context) { - if (isBound) { - // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is - // initialized before this happens though, so binding is placed at a later time, during - // normal operations that can be repeated. This check avoids calling "bindService" 2x+ - return - } - try { - val info = - context.packageManager.getPackageInfo( - MessageConstants.TRACING_APP_PACKAGE_NAME, - PackageManager.MATCH_SYSTEM_ONLY - ) - val intent = - Intent().setClassName(info.packageName, MessageConstants.TRACING_APP_ACTIVITY) - val flags = - Context.BIND_AUTO_CREATE or - Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or - Context.BIND_WAIVE_PRIORITY - context.bindService(intent, traceurConnection, flags) - } catch (e: Exception) { - Log.e(TAG, "failed to bind to Traceur's service", e) - } - } - - @WorkerThread - fun unbindFromTraceur(context: Context) { - if (isBound) { - context.unbindService(traceurConnection) - } - } - - @WorkerThread - fun startTracing(traceType: TraceConfig) { - val data = - Bundle().apply { putParcelable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) } - notifyTraceur(MessageConstants.START_WHAT, data) - } - - @WorkerThread fun stopTracing() = notifyTraceur(MessageConstants.STOP_WHAT) - - @WorkerThread - fun shareTraces(context: Context, screenRecord: Uri?) { - val replyHandler = Messenger(ShareFilesHandler(context, screenRecord, backgroundLooper)) - notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler) - } - - @WorkerThread - fun getTags(state: IssueRecordingState) { - val replyHandler = Messenger(TagsHandler(backgroundLooper, state)) - notifyTraceur(MessageConstants.TAGS_WHAT, replyTo = replyHandler) - } - - @WorkerThread - private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) { - try { - binder!!.send( - Message.obtain().apply { - this.what = what - this.data = data - this.replyTo = replyTo - } - ) - } catch (e: Exception) { - Log.e(TAG, "failed to notify Traceur", e) - } - } - - private class ShareFilesHandler( - private val context: Context, - private val screenRecord: Uri?, - looper: Looper, - ) : Handler(looper) { - - override fun handleMessage(msg: Message) { - if (MessageConstants.SHARE_WHAT == msg.what) { - shareTraces( - msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java), - msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java) - ) - } else { - throw IllegalArgumentException("received unknown msg.what: " + msg.what) - } - } - - private fun shareTraces(perfetto: Uri?, winscope: Uri?) { - val uris: List<Uri> = - mutableListOf<Uri>().apply { - perfetto?.let { add(it) } - winscope?.let { add(it) } - screenRecord?.let { add(it) } - } - val fileSharingIntent = - FileSender.buildSendIntent(context, uris) - .addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT - ) - context.startActivity(fileSharingIntent) - } - } - - private class TagsHandler(looper: Looper, private val state: IssueRecordingState) : - Handler(looper) { - - override fun handleMessage(msg: Message) { - if (MessageConstants.TAGS_WHAT == msg.what) { - val keys = msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAGS) - val values = - msg.data.getStringArrayList(MessageConstants.BUNDLE_KEY_TAG_DESCRIPTIONS) - if (keys == null || values == null) { - throw IllegalArgumentException( - "Neither keys: $keys, nor values: $values can be null" - ) - } - state.tagTitles = - keys.zip(values).map { it.first + TAG_TITLE_DELIMITER + it.second }.toSet() - } else { - throw IllegalArgumentException("received unknown msg.what: " + msg.what) - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt new file mode 100644 index 000000000000..6aaa27dd7387 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/UserAwareConnection.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recordissue + +import android.annotation.SuppressLint +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.os.IBinder +import android.os.Messenger +import android.util.Log +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread +import com.android.systemui.settings.UserContextProvider + +private const val TAG = "UserAwareConnection" +private const val BIND_FLAGS = + Context.BIND_AUTO_CREATE or + Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or + Context.BIND_WAIVE_PRIORITY + +/** ServiceConnection class that can be used to keep an IntentService alive. */ +open class UserAwareConnection( + protected val userContextProvider: UserContextProvider, + private val intent: Intent, +) : ServiceConnection { + @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) var binder: Messenger? = null + private var shouldUnBind = false + + override fun onServiceConnected(className: ComponentName, service: IBinder) { + binder = Messenger(service) + } + + override fun onServiceDisconnected(className: ComponentName) { + binder = null + } + + @SuppressLint("WrongConstant") + @WorkerThread + fun doBind() { + if (shouldUnBind) { + // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is + // initialized before this happens though, so binding is placed at a later time, during + // normal operations that can be repeated. This check avoids calling "bindService" 2x+ + return + } + try { + shouldUnBind = userContextProvider.userContext.bindService(intent, this, BIND_FLAGS) + } catch (e: Exception) { + Log.e(TAG, "failed to bind to the service", e) + } + } + + @WorkerThread + fun doUnBind() { + if (shouldUnBind) { + userContextProvider.userContext.unbindService(this) + shouldUnBind = false + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index ca518f9bc5d7..833cf424ef93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -33,7 +33,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate -import com.android.systemui.recordissue.TraceurMessageSender +import com.android.systemui.recordissue.TraceurConnection import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingController import com.android.systemui.settings.UserContextProvider @@ -75,7 +75,7 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var panelInteractor: PanelInteractor @Mock private lateinit var userContextProvider: UserContextProvider @Mock private lateinit var issueRecordingState: IssueRecordingState - @Mock private lateinit var traceurMessageSender: TraceurMessageSender + @Mock private lateinit var traceurConnection: TraceurConnection @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate @Mock private lateinit var dialog: SystemUIDialog @@ -107,7 +107,7 @@ class RecordIssueTileTest : SysuiTestCase() { dialogLauncherAnimator, panelInteractor, userContextProvider, - traceurMessageSender, + traceurConnection, Executors.newSingleThreadExecutor(), issueRecordingState, delegateFactory, @@ -169,7 +169,7 @@ class RecordIssueTileTest : SysuiTestCase() { .executeWhenUnlocked( isA(ActivityStarter.OnDismissAction::class.java), eq(false), - eq(true) + eq(true), ) } } |