1 /* <lambda>null2 * Copyright (C) 2020 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.demomode 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.os.Bundle 24 import android.os.UserHandle 25 import android.util.Log 26 import com.android.systemui.Dumpable 27 import com.android.systemui.demomode.DemoMode.ACTION_DEMO 28 import com.android.systemui.dump.DumpManager 29 import com.android.systemui.statusbar.policy.CallbackController 30 import com.android.systemui.util.Assert 31 import com.android.systemui.util.settings.GlobalSettings 32 import java.io.FileDescriptor 33 import java.io.PrintWriter 34 35 /** 36 * Handles system broadcasts for [DemoMode] 37 * 38 * Injected via [DemoModeModule] 39 */ 40 class DemoModeController constructor( 41 private val context: Context, 42 private val dumpManager: DumpManager, 43 private val globalSettings: GlobalSettings 44 ) : CallbackController<DemoMode>, Dumpable { 45 46 // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process 47 var isInDemoMode = false 48 49 var isAvailable = false 50 get() = tracker.isDemoModeAvailable 51 52 private var initialized = false 53 54 private val receivers = mutableListOf<DemoMode>() 55 private val receiverMap: Map<String, MutableList<DemoMode>> 56 57 init { 58 val m = mutableMapOf<String, MutableList<DemoMode>>() 59 DemoMode.COMMANDS.map { command -> 60 m.put(command, mutableListOf()) 61 } 62 receiverMap = m 63 } 64 65 fun initialize() { 66 if (initialized) { 67 throw IllegalStateException("Already initialized") 68 } 69 70 initialized = true 71 72 dumpManager.registerDumpable(TAG, this) 73 74 // Due to DemoModeFragment running in systemui:tuner process, we have to observe for 75 // content changes to know if the setting turned on or off 76 tracker.startTracking() 77 78 // TODO: We should probably exit demo mode if we booted up with it on 79 isInDemoMode = tracker.isInDemoMode 80 81 val demoFilter = IntentFilter() 82 demoFilter.addAction(ACTION_DEMO) 83 context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter, 84 android.Manifest.permission.DUMP, null) 85 } 86 87 override fun addCallback(listener: DemoMode) { 88 // Register this listener for its commands 89 val commands = listener.demoCommands() 90 91 commands.forEach { command -> 92 if (!receiverMap.containsKey(command)) { 93 throw IllegalStateException("Command ($command) not recognized. " + 94 "See DemoMode.java for valid commands") 95 } 96 97 receiverMap[command]!!.add(listener) 98 } 99 100 synchronized(this) { 101 receivers.add(listener) 102 } 103 104 if (isInDemoMode) { 105 listener.onDemoModeStarted() 106 } 107 } 108 109 override fun removeCallback(listener: DemoMode) { 110 synchronized(this) { 111 listener.demoCommands().forEach { command -> 112 receiverMap[command]!!.remove(listener) 113 } 114 115 receivers.remove(listener) 116 } 117 } 118 119 private fun setIsDemoModeAllowed(enabled: Boolean) { 120 // Turn off demo mode if it was on 121 if (isInDemoMode && !enabled) { 122 requestFinishDemoMode() 123 } 124 } 125 126 private fun enterDemoMode() { 127 isInDemoMode = true 128 Assert.isMainThread() 129 130 val copy: List<DemoModeCommandReceiver> 131 synchronized(this) { 132 copy = receivers.toList() 133 } 134 135 copy.forEach { r -> 136 r.onDemoModeStarted() 137 } 138 } 139 140 private fun exitDemoMode() { 141 isInDemoMode = false 142 Assert.isMainThread() 143 144 val copy: List<DemoModeCommandReceiver> 145 synchronized(this) { 146 copy = receivers.toList() 147 } 148 149 copy.forEach { r -> 150 r.onDemoModeFinished() 151 } 152 } 153 154 fun dispatchDemoCommand(command: String, args: Bundle) { 155 Assert.isMainThread() 156 157 if (DEBUG) { 158 Log.d(TAG, "dispatchDemoCommand: $command, args=$args") 159 } 160 161 if (!isAvailable) { 162 return 163 } 164 165 if (command == DemoMode.COMMAND_ENTER) { 166 enterDemoMode() 167 } else if (command == DemoMode.COMMAND_EXIT) { 168 exitDemoMode() 169 } else if (!isInDemoMode) { 170 enterDemoMode() 171 } 172 173 // See? demo mode is easy now, you just notify the listeners when their command is called 174 receiverMap[command]!!.forEach { receiver -> 175 receiver.dispatchDemoCommand(command, args) 176 } 177 } 178 179 override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { 180 pw.println("DemoModeController state -") 181 pw.println(" isInDemoMode=$isInDemoMode") 182 pw.println(" isDemoModeAllowed=$isAvailable") 183 pw.print(" receivers=[") 184 val copy: List<DemoModeCommandReceiver> 185 synchronized(this) { 186 copy = receivers.toList() 187 } 188 copy.forEach { recv -> 189 pw.print(" ${recv.javaClass.simpleName}") 190 } 191 pw.println(" ]") 192 pw.println(" receiverMap= [") 193 receiverMap.keys.forEach { command -> 194 pw.print(" $command : [") 195 val recvs = receiverMap[command]!!.map { receiver -> 196 receiver.javaClass.simpleName 197 }.joinToString(",") 198 pw.println("$recvs ]") 199 } 200 } 201 202 private val tracker = object : DemoModeAvailabilityTracker(context) { 203 override fun onDemoModeAvailabilityChanged() { 204 setIsDemoModeAllowed(isDemoModeAvailable) 205 } 206 207 override fun onDemoModeStarted() { 208 if (this@DemoModeController.isInDemoMode != isInDemoMode) { 209 enterDemoMode() 210 } 211 } 212 213 override fun onDemoModeFinished() { 214 if (this@DemoModeController.isInDemoMode != isInDemoMode) { 215 exitDemoMode() 216 } 217 } 218 } 219 220 private val broadcastReceiver = object : BroadcastReceiver() { 221 override fun onReceive(context: Context, intent: Intent) { 222 if (DEBUG) { 223 Log.v(TAG, "onReceive: $intent") 224 } 225 226 val action = intent.action 227 if (!ACTION_DEMO.equals(action)) { 228 return 229 } 230 231 val bundle = intent.extras ?: return 232 val command = bundle.getString("command", "").trim().toLowerCase() 233 if (command.length == 0) { 234 return 235 } 236 237 try { 238 dispatchDemoCommand(command, bundle) 239 } catch (t: Throwable) { 240 Log.w(TAG, "Error running demo command, intent=$intent $t") 241 } 242 } 243 } 244 245 fun requestSetDemoModeAllowed(allowed: Boolean) { 246 setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0) 247 } 248 249 fun requestStartDemoMode() { 250 setGlobal(DEMO_MODE_ON, 1) 251 } 252 253 fun requestFinishDemoMode() { 254 setGlobal(DEMO_MODE_ON, 0) 255 } 256 257 private fun setGlobal(key: String, value: Int) { 258 globalSettings.putInt(key, value) 259 } 260 } 261 262 private const val TAG = "DemoModeController" 263 private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed" 264 private const val DEMO_MODE_ON = "sysui_tuner_demo_on" 265 266 private const val DEBUG = false 267