1 /*
2  * Copyright (C) 2021 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.flags
18 
19 import android.app.Activity
20 import android.content.BroadcastReceiver
21 import android.content.Context
22 import android.content.Intent
23 import android.database.ContentObserver
24 import android.net.Uri
25 import android.os.Bundle
26 import android.os.Handler
27 import android.provider.Settings
28 import androidx.concurrent.futures.CallbackToFutureAdapter
29 import com.google.common.util.concurrent.ListenableFuture
30 import org.json.JSONException
31 import org.json.JSONObject
32 
33 class FlagManager constructor(
34     private val context: Context,
35     private val handler: Handler
36 ) : FlagReader {
37     companion object {
38         const val RECEIVING_PACKAGE = "com.android.systemui"
39         const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
40         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
41         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
42         const val FIELD_ID = "id"
43         const val FIELD_VALUE = "value"
44         const val FIELD_TYPE = "type"
45         const val FIELD_FLAGS = "flags"
46         const val TYPE_BOOLEAN = "boolean"
47         private const val SETTINGS_PREFIX = "systemui/flags"
48     }
49 
50     private val listeners: MutableSet<FlagReader.Listener> = mutableSetOf()
51     private val settingsObserver: ContentObserver = SettingsObserver()
52 
53     fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
54         val intent = Intent(ACTION_GET_FLAGS)
55         intent.setPackage(RECEIVING_PACKAGE)
56 
57         return CallbackToFutureAdapter.getFuture {
58             completer: CallbackToFutureAdapter.Completer<Any?> ->
59                 context.sendOrderedBroadcast(intent, null,
60                     object : BroadcastReceiver() {
61                         override fun onReceive(context: Context, intent: Intent) {
62                             val extras: Bundle? = getResultExtras(false)
63                             val listOfFlags: java.util.ArrayList<Flag<*>>? =
64                                 extras?.getParcelableArrayList(FIELD_FLAGS)
65                             if (listOfFlags != null) {
66                                 completer.set(listOfFlags)
67                             } else {
68                                 completer.setException(NoFlagResultsException())
69                             }
70                         }
71                     }, null, Activity.RESULT_OK, "extra data", null)
72             "QueryingFlags"
73         } as ListenableFuture<Collection<Flag<*>>>
74     }
75 
76     fun setFlagValue(id: Int, enabled: Boolean) {
77         val intent = createIntent(id)
78         intent.putExtra(FIELD_VALUE, enabled)
79 
80         context.sendBroadcast(intent)
81     }
82 
83     fun eraseFlag(id: Int) {
84         val intent = createIntent(id)
85 
86         context.sendBroadcast(intent)
87     }
88 
89     override fun isEnabled(id: Int, def: Boolean): Boolean {
90         return isEnabled(id) ?: def
91     }
92 
93     /** Returns the stored value or null if not set.  */
94     fun isEnabled(id: Int): Boolean? {
95         val data: String? = Settings.Secure.getString(
96             context.contentResolver, keyToSettingsPrefix(id))
97         if (data == null || data?.isEmpty()) {
98             return null
99         }
100         val json: JSONObject
101         try {
102             json = JSONObject(data)
103             return if (!assertType(json, TYPE_BOOLEAN)) {
104                 null
105             } else json.getBoolean(FIELD_VALUE)
106         } catch (e: JSONException) {
107             throw InvalidFlagStorageException()
108         }
109     }
110 
111     override fun addListener(listener: FlagReader.Listener) {
112         synchronized(listeners) {
113             val registerNeeded = listeners.isEmpty()
114             listeners.add(listener)
115             if (registerNeeded) {
116                 context.contentResolver.registerContentObserver(
117                     Settings.Secure.getUriFor(SETTINGS_PREFIX), true, settingsObserver)
118             }
119         }
120     }
121 
122     override fun removeListener(listener: FlagReader.Listener) {
123         synchronized(listeners) {
124             val isRegistered = !listeners.isEmpty()
125             listeners.remove(listener)
126             if (isRegistered && listeners.isEmpty()) {
127                 context.contentResolver.unregisterContentObserver(settingsObserver)
128             }
129         }
130     }
131 
132     private fun createIntent(id: Int): Intent {
133         val intent = Intent(ACTION_SET_FLAG)
134         intent.setPackage(RECEIVING_PACKAGE)
135         intent.putExtra(FIELD_ID, id)
136 
137         return intent
138     }
139 
140     fun keyToSettingsPrefix(key: Int): String {
141         return SETTINGS_PREFIX + "/" + key
142     }
143 
144     private fun assertType(json: JSONObject, type: String): Boolean {
145         return try {
146             json.getString(FIELD_TYPE) == TYPE_BOOLEAN
147         } catch (e: JSONException) {
148             false
149         }
150     }
151 
152     inner class SettingsObserver : ContentObserver(handler) {
153         override fun onChange(selfChange: Boolean, uri: Uri?) {
154             if (uri == null) {
155                 return
156             }
157             val parts = uri.pathSegments
158             val idStr = parts[parts.size - 1]
159             try {
160                 val id = idStr.toInt()
161                 listeners.forEach { l -> l.onFlagChanged(id) }
162             } catch (e: NumberFormatException) {
163                 // no-op
164             }
165         }
166     }
167 }
168 
169 class InvalidFlagStorageException : Exception("Data found but is invalid")
170 
171 class NoFlagResultsException : Exception(
172     "SystemUI failed to communicate its flags back successfully")