Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a934e9a authored by Dave Mankoff's avatar Dave Mankoff
Browse files

Add ability to read flag values to Flag Library.

The FlagManager now partially implements the FlagReader interface.

The FeatureFlagManager, part of SystemUI proper, now uses the
FlagManager to read flag values, helping to ensure consistency between
the two separate systems.

Bug: 203548827
Test: manual
Change-Id: Ia3b103f783a846cfe9437cd8e6b987962cebabde
parent 93c9d9d6
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -179,8 +179,7 @@ android_library {
        "src/**/*.kt",
        "src/**/*.java",
        "src/**/I*.aidl",
        "src-release/**/*.kt",
        "src-release/**/*.java",
        ":ReleaseJavaFiles",
    ],
    static_libs: [
        "SystemUIAnimationLib",
+44 −2
Original line number Diff line number Diff line
@@ -18,16 +18,22 @@ package com.android.systemui.flags

import android.content.Context
import android.content.Intent
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(val context: Context) {
class FlagManager constructor(val context: Context) : FlagReader {
    companion object {
        const val RECEIVING_PACKAGE = "com.android.systemui"
        const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
        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 TYPE_BOOLEAN = "boolean"
        private const val SETTINGS_PREFIX = "systemui/flags"
    }

    fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
@@ -54,6 +60,28 @@ class FlagManager constructor(val context: Context) {
        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.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()
        }
    }

    private fun createIntent(id: Int): Intent {
        val intent = Intent(ACTION_SET_FLAG)
        intent.setPackage(RECEIVING_PACKAGE)
@@ -61,4 +89,18 @@ class FlagManager constructor(val context: Context) {

        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
        }
    }
}

class InvalidFlagStorageException : Exception("Data found but is invalid")
 No newline at end of file
+13 −15
Original line number Diff line number Diff line
@@ -13,28 +13,26 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.flags;

package com.android.systemui.flags

/**
 * Plugin for loading flag values
 */
public interface FlagReader {
interface FlagReader {
    /** Returns a boolean value for the given flag.  */
    default boolean isEnabled(int id, boolean def) {
        return def;
    fun isEnabled(id: Int, def: Boolean): Boolean {
        return def
    }

    /** Add a listener to be alerted when any flag changes.  */
    default void addListener(Listener listener) {}
    fun addListener(listener: Listener) {}

    /** Remove a listener to be alerted when any flag changes.  */
    default void removeListener(Listener listener) {}
    fun removeListener(listener: Listener) {}

    /** A simple listener to be alerted when a flag changes.  */
    interface Listener {
        /**  */
        void onFlagChanged(int id);
        fun onFlagChanged(id: Int)
    }
}
 No newline at end of file
+13 −36
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import androidx.annotation.NonNull;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.util.settings.SecureSettings;

import org.json.JSONException;
import org.json.JSONObject;
@@ -58,18 +59,16 @@ import javax.inject.Inject;
public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
    private static final String TAG = "SysUIFlags";

    private static final String SYSPROP_PREFIX = "persist.systemui.flag_";
    private static final String FIELD_TYPE = "type";
    private static final String TYPE_BOOLEAN = "boolean";
    private final SystemPropertiesHelper mSystemPropertiesHelper;

    private final FlagManager mFlagManager;
    private final SecureSettings mSecureSettings;
    private final Map<Integer, Boolean> mBooleanFlagCache = new HashMap<>();

    @Inject
    public FeatureFlagManager(SystemPropertiesHelper systemPropertiesHelper, Context context,
    public FeatureFlagManager(FlagManager flagManager,
            SecureSettings secureSettings, Context context,
            DumpManager dumpManager) {
        mSystemPropertiesHelper = systemPropertiesHelper;

        mFlagManager = flagManager;
        mSecureSettings = secureSettings;
        IntentFilter filter = new IntentFilter(ACTION_SET_FLAG);
        context.registerReceiver(mReceiver, filter, FLAGS_PERMISSION, null);
        dumpManager.registerDumpable(TAG, this);
@@ -88,20 +87,10 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {

    /** Returns the stored value or null if not set. */
    private Boolean isEnabledInternal(int id) {
        String data = mSystemPropertiesHelper.get(keyToSysPropKey(id));
        if (data.isEmpty()) {
            return null;
        }
        JSONObject json;
        try {
            json = new JSONObject(data);
            if (!assertType(json, TYPE_BOOLEAN)) {
                return null;
            }

            return json.getBoolean(FIELD_VALUE);
        } catch (JSONException e) {
            eraseInternal(id);  // Don't restart SystemUI in this case.
            return mFlagManager.isEnabled(id);
        } catch (Exception e) {
            eraseInternal(id);
        }
        return null;
    }
@@ -116,9 +105,9 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {

        JSONObject json = new JSONObject();
        try {
            json.put(FIELD_TYPE, TYPE_BOOLEAN);
            json.put(FlagManager.FIELD_TYPE, FlagManager.TYPE_BOOLEAN);
            json.put(FIELD_VALUE, value);
            mSystemPropertiesHelper.set(keyToSysPropKey(id), json.toString());
            mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), json.toString());
            Log.i(TAG, "Set id " + id + " to " + value);
            restartSystemUI();
        } catch (JSONException e) {
@@ -135,7 +124,7 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
    /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
    private void eraseInternal(int id) {
        // We can't actually "erase" things from sysprops, but we can set them to empty!
        mSystemPropertiesHelper.set(keyToSysPropKey(id), "");
        mSecureSettings.putString(mFlagManager.keyToSettingsPrefix(id), "");
        Log.i(TAG, "Erase id " + id);
    }

@@ -151,18 +140,6 @@ public class FeatureFlagManager implements FlagReader, FlagWriter, Dumpable {
        System.exit(0);
    }

    private static String keyToSysPropKey(int key) {
        return SYSPROP_PREFIX + key;
    }

    private static boolean assertType(JSONObject json, String type) {
        try {
            return json.getString(FIELD_TYPE).equals(TYPE_BOOLEAN);
        } catch (JSONException e) {
            return false;
        }
    }

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
+30 −0
Original line number Diff line number Diff line
/*
 * 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.content.Context
import dagger.Module
import dagger.Provides

@Module
object FlagsModule {
    @JvmStatic
    @Provides
    fun provideFlagManager(context: Context): FlagManager {
        return FlagManager(context)
    }
}
 No newline at end of file
Loading