• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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