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.annotation.SuppressLint 20 import android.content.ContentResolver 21 import android.content.Context 22 import android.database.ContentObserver 23 import android.os.Handler 24 import androidx.annotation.VisibleForTesting 25 import androidx.annotation.WorkerThread 26 import com.android.systemui.dagger.SysUISingleton 27 import com.android.systemui.dagger.qualifiers.Background 28 import com.android.systemui.recordissue.RecordIssueModule.Companion.TILE_SPEC 29 import com.android.systemui.res.R 30 import com.android.systemui.settings.UserFileManager 31 import com.android.systemui.settings.UserTracker 32 import com.android.systemui.util.settings.GlobalSettings 33 import com.android.traceur.PresetTraceConfigs 34 import com.android.traceur.TraceConfig 35 import java.util.concurrent.CopyOnWriteArrayList 36 import javax.inject.Inject 37 38 @SysUISingleton 39 class IssueRecordingState 40 @Inject 41 constructor( 42 private val userTracker: UserTracker, 43 private val userFileManager: UserFileManager, 44 @Background bgHandler: Handler, 45 private val resolver: ContentResolver, 46 private val globalSettings: GlobalSettings, 47 ) { 48 49 private val prefs 50 get() = 51 userFileManager.getSharedPreferences( 52 TILE_SPEC, 53 Context.MODE_PRIVATE, 54 userTracker.userId, 55 ) 56 57 val customTraceState = CustomTraceState(prefs) 58 59 var takeBugreport 60 get() = prefs.getBoolean(KEY_TAKE_BUG_REPORT, false) 61 set(value) = prefs.edit().putBoolean(KEY_TAKE_BUG_REPORT, value).apply() 62 63 var recordScreen 64 get() = prefs.getBoolean(KEY_RECORD_SCREEN, false) 65 set(value) = prefs.edit().putBoolean(KEY_RECORD_SCREEN, value).apply() 66 67 var hasUserApprovedScreenRecording 68 get() = prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false) 69 private set(value) = prefs.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, value).apply() 70 71 // Store the index of the issue type because res ids are generated at compile time and change 72 // in value from one build to another. The index will not change between package versions. 73 private var issueTypeIndex: Int 74 get() = prefs.getInt(KEY_ISSUE_TYPE_INDEX, ISSUE_TYPE_NOT_SET) 75 set(value) = prefs.edit().putInt(KEY_ISSUE_TYPE_INDEX, value).apply() 76 77 var issueTypeRes 78 get() = 79 // If the user has never used the record issue tile, we don't show a default issue type 80 if (issueTypeIndex == ISSUE_TYPE_NOT_SET) ISSUE_TYPE_NOT_SET 81 else ALL_ISSUE_TYPES.keys.toIntArray()[issueTypeIndex] 82 set(value) { 83 issueTypeIndex = ALL_ISSUE_TYPES.keys.toIntArray().indexOf(value) 84 } 85 86 val traceConfig: TraceConfig 87 get() = ALL_ISSUE_TYPES[issueTypeRes] ?: customTraceState.traceConfig 88 89 // The 1st part of the title before the ": " is the tag, and the 2nd part is the description 90 var tagTitles: Set<String> 91 get() = prefs.getStringSet(KEY_TAG_TITLES, emptySet()) ?: emptySet() 92 set(value) = prefs.edit().putStringSet(KEY_TAG_TITLES, value).apply() 93 94 private val listeners = CopyOnWriteArrayList<Runnable>() 95 96 @VisibleForTesting 97 val onRecordingChangeListener = 98 object : ContentObserver(bgHandler) { onChangenull99 override fun onChange(selfChange: Boolean) { 100 isRecording = globalSettings.getInt(KEY_ONGOING_ISSUE_RECORDING, 0) == 1 101 listeners.forEach(Runnable::run) 102 } 103 } 104 105 /** 106 * isRecording is purposely always set to false at the initialization of the record issue qs 107 * tile. We want to avoid a situation where the System UI crashed / the device was restarted in 108 * the middle of a trace session and the QS tile is in an active state even though no tracing is 109 * ongoing. 110 */ 111 var isRecording = false 112 @WorkerThread 113 set(value) { 114 globalSettings.putInt(KEY_ONGOING_ISSUE_RECORDING, if (value) 1 else 0) 115 field = value 116 } 117 markUserApprovalForScreenRecordingnull118 fun markUserApprovalForScreenRecording() { 119 hasUserApprovedScreenRecording = true 120 } 121 122 @WorkerThread 123 @SuppressLint("RegisterContentObserverViaContentResolver") addListenernull124 fun addListener(listener: Runnable) { 125 if (listeners.isEmpty()) { 126 resolver.registerContentObserver( 127 globalSettings.getUriFor(KEY_ONGOING_ISSUE_RECORDING), 128 false, 129 onRecordingChangeListener, 130 ) 131 } 132 listeners.add(listener) 133 } 134 135 @WorkerThread 136 @SuppressLint("RegisterContentObserverViaContentResolver") removeListenernull137 fun removeListener(listener: Runnable) { 138 listeners.remove(listener) 139 if (listeners.isEmpty()) { 140 resolver.unregisterContentObserver(onRecordingChangeListener) 141 } 142 } 143 144 companion object { 145 private const val KEY_TAKE_BUG_REPORT = "key_takeBugReport" 146 private const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord" 147 private const val KEY_RECORD_SCREEN = "key_recordScreen" 148 private const val KEY_TAG_TITLES = "key_tagTitles" 149 private const val KEY_ONGOING_ISSUE_RECORDING = "issueRecordingOngoing" 150 const val KEY_ISSUE_TYPE_INDEX = "key_issueTypeIndex" 151 const val ISSUE_TYPE_NOT_SET = -1 152 const val TAG_TITLE_DELIMITER = ": " 153 154 val ALL_ISSUE_TYPES: LinkedHashMap<Int, TraceConfig?> = 155 linkedMapOf( 156 Pair(R.string.performance, PresetTraceConfigs.getPerformanceConfig()), 157 Pair(R.string.user_interface, PresetTraceConfigs.getUiConfig()), 158 Pair(R.string.battery, PresetTraceConfigs.getBatteryConfig()), 159 Pair(R.string.thermal, PresetTraceConfigs.getThermalConfig()), 160 Pair(R.string.custom, null), // Null means we are using a custom trace config 161 ) 162 } 163 } 164