Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4cbc7f31 authored by Stefan Andonian's avatar Stefan Andonian
Browse files

Persist User Selections in Record Issue Tile Dialog

Bug: 305049544
Test: Tested this manually on my device.
Flag: com.android.systemui.record_issue_qs_tile STAGING
Change-Id: Ib5e5776d76744e6b1d3136e039277edfdac06b49
parent 37d905d0
Loading
Loading
Loading
Loading
+4 −10
Original line number Diff line number Diff line
@@ -52,7 +52,6 @@ import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.Executor
import javax.inject.Inject

@@ -131,15 +130,11 @@ constructor(
        }
    }

    private fun startIssueRecordingService(screenRecord: Boolean, traceType: PresetTraceType) =
    private fun startIssueRecordingService() =
        PendingIntent.getForegroundService(
                userContextProvider.userContext,
                RecordingService.REQUEST_CODE,
                IssueRecordingService.getStartIntent(
                    userContextProvider.userContext,
                    screenRecord,
                    traceType
                ),
                IssueRecordingService.getStartIntent(userContextProvider.userContext),
                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
            )
            .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
@@ -157,7 +152,7 @@ constructor(
        val dialog: AlertDialog =
            delegateFactory
                .create {
                    startIssueRecordingService(it.screenRecord, it.traceType)
                    startIssueRecordingService()
                    dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
                    panelInteractor.collapsePanels()
                }
@@ -169,8 +164,7 @@ constructor(
                if (expandable != null && !keyguardStateController.isShowing) {
                    expandable
                        .dialogTransitionController(DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC))
                        ?.let { dialogTransitionAnimator.show(dialog, it) }
                        ?: dialog.show()
                        ?.let { dialogTransitionAnimator.show(dialog, it) } ?: dialog.show()
                } else {
                    dialog.show()
                }
+0 −21
Original line number Diff line number Diff line
/*
 * 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 com.android.traceur.TraceUtils.PresetTraceType

data class IssueRecordingConfig(val screenRecord: Boolean, val traceType: PresetTraceType)
+13 −20
Original line number Diff line number Diff line
@@ -35,8 +35,6 @@ import com.android.systemui.screenrecord.RecordingService
import com.android.systemui.screenrecord.RecordingServiceStrings
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE
import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.Executor
import javax.inject.Inject

@@ -76,15 +74,10 @@ constructor(
        when (intent?.action) {
            ACTION_START -> {
                bgExecutor.execute {
                    traceurMessageSender.startTracing(
                        intent.getSerializableExtra(
                            INTENT_EXTRA_TRACE_TYPE,
                            PresetTraceType::class.java
                        )
                    )
                    traceurMessageSender.startTracing(issueRecordingState.traceType)
                }
                issueRecordingState.isRecording = true
                if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
                if (!issueRecordingState.recordScreen) {
                    // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
                    // will circumvent the RecordingService's screen recording start code.
                    return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
@@ -107,7 +100,7 @@ constructor(
                    )

                    val screenRecording = intent.getParcelableExtra(EXTRA_PATH, Uri::class.java)
                    if (issueRecordingState.takeBugReport) {
                    if (issueRecordingState.takeBugreport) {
                        iActivityManager.requestBugReportWithExtraAttachment(screenRecording)
                    } else {
                        traceurMessageSender.shareTraces(applicationContext, screenRecording)
@@ -130,7 +123,6 @@ constructor(
    companion object {
        private const val TAG = "IssueRecordingService"
        private const val CHANNEL_ID = "issue_record"
        private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"

        /**
         * Get an intent to stop the issue recording service.
@@ -148,35 +140,36 @@ constructor(
         *
         * @param context Context from the requesting activity
         */
        fun getStartIntent(
            context: Context,
            screenRecord: Boolean,
            traceType: PresetTraceType,
        ): Intent =
            Intent(context, IssueRecordingService::class.java)
                .setAction(ACTION_START)
                .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
                .putExtra(INTENT_EXTRA_TRACE_TYPE, traceType)
        fun getStartIntent(context: Context): Intent =
            Intent(context, IssueRecordingService::class.java).setAction(ACTION_START)
    }
}

private class IrsStrings(private val res: Resources) : RecordingServiceStrings(res) {
    override val title
        get() = res.getString(R.string.issuerecord_title)

    override val notificationChannelDescription
        get() = res.getString(R.string.issuerecord_channel_description)

    override val startErrorResId
        get() = R.string.issuerecord_start_error

    override val startError
        get() = res.getString(R.string.issuerecord_start_error)

    override val saveErrorResId
        get() = R.string.issuerecord_save_error

    override val saveError
        get() = res.getString(R.string.issuerecord_save_error)

    override val ongoingRecording
        get() = res.getString(R.string.issuerecord_ongoing_screen_only)

    override val backgroundProcessingLabel
        get() = res.getString(R.string.issuerecord_background_processing_label)

    override val saveTitle
        get() = res.getString(R.string.issuerecord_save_title)
}
+58 −3
Original line number Diff line number Diff line
@@ -16,16 +16,51 @@

package com.android.systemui.recordissue

import android.content.Context
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.traceur.TraceUtils.PresetTraceType
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject

@SysUISingleton
class IssueRecordingState @Inject constructor() {
class IssueRecordingState
@Inject
constructor(
    userTracker: UserTracker,
    userFileManager: UserFileManager,
) {

    private val listeners = CopyOnWriteArrayList<Runnable>()
    private val prefs =
        userFileManager.getSharedPreferences(
            RecordIssueTile.TILE_SPEC,
            Context.MODE_PRIVATE,
            userTracker.userId
        )

    var takeBugreport
        get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false)
        set(value) = prefs.edit().putBoolean(KEY_TAKE_BUG_REPORT, value).apply()

    var recordScreen
        get() = prefs.getBoolean(KEY_RECORD_SCREEN, false)
        set(value) = prefs.edit().putBoolean(KEY_RECORD_SCREEN, value).apply()

    var hasUserApprovedScreenRecording
        get() = prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)
        private set(value) = prefs.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, value).apply()

    var issueTypeRes
        get() = prefs.getInt(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
        set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_RES, value).apply()

    val traceType: PresetTraceType
        get() = ALL_ISSUE_TYPES[issueTypeRes] ?: PresetTraceType.UNSET

    var takeBugReport: Boolean = false
    private val listeners = CopyOnWriteArrayList<Runnable>()

    var isRecording = false
        set(value) {
@@ -33,6 +68,10 @@ class IssueRecordingState @Inject constructor() {
            listeners.forEach(Runnable::run)
        }

    fun markUserApprovalForScreenRecording() {
        hasUserApprovedScreenRecording = true
    }

    fun addListener(listener: Runnable) {
        listeners.add(listener)
    }
@@ -40,4 +79,20 @@ class IssueRecordingState @Inject constructor() {
    fun removeListener(listener: Runnable) {
        listeners.remove(listener)
    }

    companion object {
        private const val KEY_TAKE_BUG_REPORT = "key_takeBugReport"
        private const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
        private const val KEY_RECORD_SCREEN = "key_recordScreen"
        const val KEY_ISSUE_TYPE_RES = "key_issueTypeRes"
        const val ISSUE_TYPE_NOT_SET = -1

        val ALL_ISSUE_TYPES: Map<Int, PresetTraceType> =
            hashMapOf(
                Pair(R.string.performance, PresetTraceType.PERFORMANCE),
                Pair(R.string.user_interface, PresetTraceType.UI),
                Pair(R.string.battery, PresetTraceType.BATTERY),
                Pair(R.string.thermal, PresetTraceType.THERMAL)
            )
    }
}
+53 −69
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@
package com.android.systemui.recordissue

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.AlertDialog.BUTTON_POSITIVE
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
@@ -41,18 +41,16 @@ import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.recordissue.IssueRecordingState.Companion.ALL_ISSUE_TYPES
import com.android.systemui.recordissue.IssueRecordingState.Companion.ISSUE_TYPE_NOT_SET
import com.android.systemui.recordissue.IssueRecordingState.Companion.KEY_ISSUE_TYPE_RES
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE
import com.android.traceur.TraceUtils.PresetTraceType
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
import java.util.function.Consumer

class RecordIssueDialogDelegate
@AssistedInject
@@ -64,31 +62,20 @@ constructor(
    @Main private val mainExecutor: Executor,
    private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
    private val userFileManager: UserFileManager,
    private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
    private val issueRecordingState: IssueRecordingState,
    private val state: IssueRecordingState,
    private val traceurMessageSender: TraceurMessageSender,
    @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
    @Assisted private val onStarted: Runnable,
) : SystemUIDialog.Delegate {

    private val issueTypeOptions: Map<Int, PresetTraceType> =
        hashMapOf(
            Pair(R.string.performance, PresetTraceType.PERFORMANCE),
            Pair(R.string.user_interface, PresetTraceType.UI),
            Pair(R.string.battery, PresetTraceType.BATTERY),
            Pair(R.string.thermal, PresetTraceType.THERMAL)
        )
    private var selectedIssueType: PresetTraceType? = null

    /** To inject dependencies and allow for easier testing */
    @AssistedFactory
    interface Factory {
        /** Create a dialog object */
        fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
        fun create(onStarted: Runnable): RecordIssueDialogDelegate
    }

    @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
    @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var bugReportSwitch: Switch
    private lateinit var issueTypeButton: Button

    @MainThread
@@ -97,21 +84,8 @@ constructor(
            setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
            setTitle(context.getString(R.string.qs_record_issue_label))
            setIcon(R.drawable.qs_record_issue_icon_off)
            setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
            setPositiveButton(
                R.string.qs_record_issue_start,
                { _, _ ->
                    issueRecordingState.takeBugReport = bugReportSwitch.isChecked
                    onStarted.accept(
                        IssueRecordingConfig(
                            screenRecordSwitch.isChecked,
                            selectedIssueType ?: PresetTraceType.UNSET
                        )
                    )
                    dismiss()
                },
                false
            )
            setNegativeButton(R.string.cancel) { _, _ -> }
            setPositiveButton(R.string.qs_record_issue_start) { _, _ -> onStarted.run() }
        }
        bgExecutor.execute { traceurMessageSender.bindToTraceur(dialog.context) }
    }
@@ -121,23 +95,40 @@ constructor(
    @MainThread
    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
        dialog.apply {
            window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
            window?.setGravity(Gravity.CENTER)
            window?.apply {
                addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
                setGravity(Gravity.CENTER)
            }

            screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
            screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
                if (isEnabled) {
            screenRecordSwitch =
                requireViewById<Switch>(R.id.screenrecord_switch).apply {
                    isChecked = state.recordScreen
                    setOnCheckedChangeListener { _, isChecked ->
                        state.recordScreen = isChecked
                        if (isChecked) {
                            bgExecutor.execute { onScreenRecordSwitchClicked() }
                        }
                    }
            bugReportSwitch = requireViewById(R.id.bugreport_switch)
            val startButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
            issueTypeButton = requireViewById(R.id.issue_type_button)
            issueTypeButton.setOnClickListener {
                onIssueTypeClicked(context) { startButton.isEnabled = true }
                }

            requireViewById<Switch>(R.id.bugreport_switch).apply {
                isChecked = state.takeBugreport
                setOnCheckedChangeListener { _, isChecked -> state.takeBugreport = isChecked }
            }

            issueTypeButton =
                requireViewById<Button>(R.id.issue_type_button).apply {
                    val startButton = dialog.getButton(BUTTON_POSITIVE)
                    if (state.issueTypeRes != ISSUE_TYPE_NOT_SET) {
                        setText(state.issueTypeRes)
                    } else {
                        startButton.isEnabled = false
                    }
                    setOnClickListener {
                        onIssueTypeClicked(context) { startButton.isEnabled = true }
                    }
                }
        }
    }

    @WorkerThread
@@ -160,45 +151,38 @@ constructor(
            SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
        )

        if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
            val prefs =
                userFileManager.getSharedPreferences(
                    RecordIssueTile.TILE_SPEC,
                    Context.MODE_PRIVATE,
                    userTracker.userId
                )
            if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
        if (
            flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING) &&
                !state.hasUserApprovedScreenRecording
        ) {
            mainExecutor.execute {
                    ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
                ScreenCapturePermissionDialogDelegate(factory, state).createDialog().apply {
                    setOnCancelListener { screenRecordSwitch.isChecked = false }
                    show()
                }
            }
        }
    }
    }

    @MainThread
    private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) {
        val popupMenu = PopupMenu(context, issueTypeButton)

        issueTypeOptions.keys.forEach {
        ALL_ISSUE_TYPES.keys.forEach {
            popupMenu.menu.add(it).apply {
                setIcon(R.drawable.arrow_pointing_down)
                if (issueTypeOptions[it] != selectedIssueType) {
                if (it != state.issueTypeRes) {
                    iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
                }
                intent = Intent().putExtra(INTENT_EXTRA_TRACE_TYPE, issueTypeOptions[it])
                intent = Intent().putExtra(KEY_ISSUE_TYPE_RES, it)
            }
        }
        popupMenu.apply {
            setOnMenuItemClickListener {
                issueTypeButton.text = it.title
                selectedIssueType =
                    it.intent?.getSerializableExtra(
                        INTENT_EXTRA_TRACE_TYPE,
                        PresetTraceType::class.java
                    )
                state.issueTypeRes =
                    it.intent?.getIntExtra(KEY_ISSUE_TYPE_RES, ISSUE_TYPE_NOT_SET)
                        ?: ISSUE_TYPE_NOT_SET
                onIssueTypeSelected.run()
                true
            }
Loading