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")