/* * Copyright (C) 2020 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.demomode import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Bundle import android.os.UserHandle import android.util.Log import com.android.systemui.Dumpable import com.android.systemui.demomode.DemoMode.ACTION_DEMO import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.util.Assert import com.android.systemui.util.settings.GlobalSettings import java.io.FileDescriptor import java.io.PrintWriter /** * Handles system broadcasts for [DemoMode] * * Injected via [DemoModeModule] */ class DemoModeController constructor( private val context: Context, private val dumpManager: DumpManager, private val globalSettings: GlobalSettings ) : CallbackController, Dumpable { // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process var isInDemoMode = false var isAvailable = false get() = tracker.isDemoModeAvailable private var initialized = false private val receivers = mutableListOf() private val receiverMap: Map> init { val m = mutableMapOf>() DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) } receiverMap = m } fun initialize() { if (initialized) { throw IllegalStateException("Already initialized") } initialized = true dumpManager.registerDumpable(TAG, this) // Due to DemoModeFragment running in systemui:tuner process, we have to observe for // content changes to know if the setting turned on or off tracker.startTracking() // TODO: We should probably exit demo mode if we booted up with it on isInDemoMode = tracker.isInDemoMode val demoFilter = IntentFilter() demoFilter.addAction(ACTION_DEMO) context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter, android.Manifest.permission.DUMP, null) } override fun addCallback(listener: DemoMode) { // Register this listener for its commands val commands = listener.demoCommands() commands.forEach { command -> if (!receiverMap.containsKey(command)) { throw IllegalStateException("Command ($command) not recognized. " + "See DemoMode.java for valid commands") } receiverMap[command]!!.add(listener) } synchronized(this) { receivers.add(listener) } if (isInDemoMode) { listener.onDemoModeStarted() } } override fun removeCallback(listener: DemoMode) { synchronized(this) { listener.demoCommands().forEach { command -> receiverMap[command]!!.remove(listener) } receivers.remove(listener) } } private fun setIsDemoModeAllowed(enabled: Boolean) { // Turn off demo mode if it was on if (isInDemoMode && !enabled) { requestFinishDemoMode() } } private fun enterDemoMode() { isInDemoMode = true Assert.isMainThread() val copy: List synchronized(this) { copy = receivers.toList() } copy.forEach { r -> r.onDemoModeStarted() } } private fun exitDemoMode() { isInDemoMode = false Assert.isMainThread() val copy: List synchronized(this) { copy = receivers.toList() } copy.forEach { r -> r.onDemoModeFinished() } } fun dispatchDemoCommand(command: String, args: Bundle) { Assert.isMainThread() if (DEBUG) { Log.d(TAG, "dispatchDemoCommand: $command, args=$args") } if (!isAvailable) { return } if (command == DemoMode.COMMAND_ENTER) { enterDemoMode() } else if (command == DemoMode.COMMAND_EXIT) { exitDemoMode() } else if (!isInDemoMode) { enterDemoMode() } // See? demo mode is easy now, you just notify the listeners when their command is called receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) } } override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { pw.println("DemoModeController state -") pw.println(" isInDemoMode=$isInDemoMode") pw.println(" isDemoModeAllowed=$isAvailable") pw.print(" receivers=[") val copy: List synchronized(this) { copy = receivers.toList() } copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") } pw.println(" ]") pw.println(" receiverMap= [") receiverMap.keys.forEach { command -> pw.print(" $command : [") val recvs = receiverMap[command]!!.map { receiver -> receiver.javaClass.simpleName }.joinToString(",") pw.println("$recvs ]") } } private val tracker = object : DemoModeAvailabilityTracker(context) { override fun onDemoModeAvailabilityChanged() { setIsDemoModeAllowed(isDemoModeAvailable) } override fun onDemoModeStarted() { if (this@DemoModeController.isInDemoMode != isInDemoMode) { enterDemoMode() } } override fun onDemoModeFinished() { if (this@DemoModeController.isInDemoMode != isInDemoMode) { exitDemoMode() } } } private val broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (DEBUG) { Log.v(TAG, "onReceive: $intent") } val action = intent.action if (!ACTION_DEMO.equals(action)) { return } val bundle = intent.extras ?: return val command = bundle.getString("command", "").trim().toLowerCase() if (command.length == 0) { return } try { dispatchDemoCommand(command, bundle) } catch (t: Throwable) { Log.w(TAG, "Error running demo command, intent=$intent $t") } } } fun requestSetDemoModeAllowed(allowed: Boolean) { setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0) } fun requestStartDemoMode() { setGlobal(DEMO_MODE_ON, 1) } fun requestFinishDemoMode() { setGlobal(DEMO_MODE_ON, 0) } private fun setGlobal(key: String, value: Int) { globalSettings.putInt(key, value) } } private const val TAG = "DemoModeController" private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed" private const val DEMO_MODE_ON = "sysui_tuner_demo_on" private const val DEBUG = false