/* * Copyright (C) 2021 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.flags import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.database.ContentObserver import android.net.Uri import android.os.Bundle import android.os.Handler import android.provider.Settings import androidx.concurrent.futures.CallbackToFutureAdapter import com.google.common.util.concurrent.ListenableFuture import org.json.JSONException import org.json.JSONObject class FlagManager constructor( private val context: Context, private val handler: Handler ) : FlagReader { companion object { const val RECEIVING_PACKAGE = "com.android.systemui" const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG" const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS" const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS" const val FIELD_ID = "id" const val FIELD_VALUE = "value" const val FIELD_TYPE = "type" const val FIELD_FLAGS = "flags" const val TYPE_BOOLEAN = "boolean" private const val SETTINGS_PREFIX = "systemui/flags" } private val listeners: MutableSet = mutableSetOf() private val settingsObserver: ContentObserver = SettingsObserver() fun getFlagsFuture(): ListenableFuture>> { val intent = Intent(ACTION_GET_FLAGS) intent.setPackage(RECEIVING_PACKAGE) return CallbackToFutureAdapter.getFuture { completer: CallbackToFutureAdapter.Completer -> context.sendOrderedBroadcast(intent, null, object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val extras: Bundle? = getResultExtras(false) val listOfFlags: java.util.ArrayList>? = extras?.getParcelableArrayList(FIELD_FLAGS) if (listOfFlags != null) { completer.set(listOfFlags) } else { completer.setException(NoFlagResultsException()) } } }, null, Activity.RESULT_OK, "extra data", null) "QueryingFlags" } as ListenableFuture>> } fun setFlagValue(id: Int, enabled: Boolean) { val intent = createIntent(id) intent.putExtra(FIELD_VALUE, enabled) context.sendBroadcast(intent) } fun eraseFlag(id: Int) { val intent = createIntent(id) context.sendBroadcast(intent) } override fun isEnabled(id: Int, def: Boolean): Boolean { return isEnabled(id) ?: def } /** Returns the stored value or null if not set. */ fun isEnabled(id: Int): Boolean? { val data: String? = Settings.Secure.getString( context.contentResolver, keyToSettingsPrefix(id)) if (data == null || data?.isEmpty()) { return null } val json: JSONObject try { json = JSONObject(data) return if (!assertType(json, TYPE_BOOLEAN)) { null } else json.getBoolean(FIELD_VALUE) } catch (e: JSONException) { throw InvalidFlagStorageException() } } override fun addListener(listener: FlagReader.Listener) { synchronized(listeners) { val registerNeeded = listeners.isEmpty() listeners.add(listener) if (registerNeeded) { context.contentResolver.registerContentObserver( Settings.Secure.getUriFor(SETTINGS_PREFIX), true, settingsObserver) } } } override fun removeListener(listener: FlagReader.Listener) { synchronized(listeners) { val isRegistered = !listeners.isEmpty() listeners.remove(listener) if (isRegistered && listeners.isEmpty()) { context.contentResolver.unregisterContentObserver(settingsObserver) } } } private fun createIntent(id: Int): Intent { val intent = Intent(ACTION_SET_FLAG) intent.setPackage(RECEIVING_PACKAGE) intent.putExtra(FIELD_ID, id) return intent } fun keyToSettingsPrefix(key: Int): String { return SETTINGS_PREFIX + "/" + key } private fun assertType(json: JSONObject, type: String): Boolean { return try { json.getString(FIELD_TYPE) == TYPE_BOOLEAN } catch (e: JSONException) { false } } inner class SettingsObserver : ContentObserver(handler) { override fun onChange(selfChange: Boolean, uri: Uri?) { if (uri == null) { return } val parts = uri.pathSegments val idStr = parts[parts.size - 1] try { val id = idStr.toInt() listeners.forEach { l -> l.onFlagChanged(id) } } catch (e: NumberFormatException) { // no-op } } } } class InvalidFlagStorageException : Exception("Data found but is invalid") class NoFlagResultsException : Exception( "SystemUI failed to communicate its flags back successfully")