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

Commit 27f4ca52 authored by Dave Mankoff's avatar Dave Mankoff
Browse files

A new Client-Side FeatureFlags library.

This is just a stub at the moment. Follow-on cls contain more
implementation.

This cl describes the basic shape for usage:

  BooleanFlag FOOBAR = FeatureFlags.booleanFlag("foo", "bar", false);
  FeatureFlags.getInstance().isEnabled(FOOBAR);

Defined are 4 flags types:
  - BooleanFlag: can be either true or false. Held constant until
        device restart.
  - DynamicBooleanFlag: can be either true or false. Can change while
        a process is running.
  - FusedOnFlag: Always true.
  - FusedOffFlag: Always false.

More flag types are intended (Resource & SystemProperty backed,
Strings, Floats, Ints, etc). but this cl represents the MVP.

More about the details of this client can be read at:
http://go/android-flagging-dd

Test: m Android-Flags
Bug: 279054964
Change-Id: I0e455bf69f01dc675ec8bf64e584e2beff4e72ce
parent d756cad8
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.flags;

import android.annotation.NonNull;

/**
 * A flag representing a true or false value.
 *
 * The value will always be the same during the lifetime of the process it is read in.
 *
 * @hide
 */
public class BooleanFlag implements Flag<Boolean> {
    private final String mNamespace;
    private final String mName;
    private final boolean mDefault;

    /**
     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
     * @param name A name for this flag.
     * @param defaultValue The value of this flag if no other override is present.
     */
    BooleanFlag(String namespace, String name, boolean defaultValue) {
        mNamespace = namespace;
        mName = name;
        mDefault = defaultValue;
    }

    @Override
    @NonNull
    public Boolean getDefault() {
        return mDefault;
    }

    @Override
    @NonNull
    public String getNamespace() {
        return mNamespace;
    }

    @Override
    @NonNull
    public String getName() {
        return mName;
    }

    @Override
    @NonNull
    public String toString() {
        return getNamespace() + "." + getName() + "[" + getDefault() + "]";
    }
}
+62 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.flags;

/**
 * A flag representing a true or false value.
 *
 * The value may be different from one read to the next.
 *
 * @hide
 */
public class DynamicBooleanFlag implements DynamicFlag<Boolean> {

    private final String mNamespace;
    private final String mName;
    private final boolean mDefault;

    /**
     * @param namespace A namespace for this flag. See {@link android.provider.DeviceConfig}.
     * @param name A name for this flag.
     * @param defaultValue The value of this flag if no other override is present.
     */
    DynamicBooleanFlag(String namespace, String name, boolean defaultValue) {
        mNamespace = namespace;
        mName = name;
        mDefault = defaultValue;
    }

    @Override
    public String getNamespace() {
        return mNamespace;
    }

    @Override
    public String getName() {
        return mName;
    }

    @Override
    public Boolean getDefault() {
        return mDefault;
    }

    @Override
    public String toString() {
        return getNamespace() + "." + getName() + "[" + getDefault() + "]";
    }
}
+27 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.flags;

/**
 * A flag for which the value may be different from one read to the next.
 *
 * @param <T> The type of value that this flag stores. E.g. Boolean or String.
 *
 * @hide
 */
public interface DynamicFlag<T> extends Flag<T> {
}
+131 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.flags;

import android.annotation.NonNull;

import java.util.HashSet;
import java.util.Set;

/**
 * A class for querying constants from the system - primarily booleans.
 *
 * Clients using this class can define their flags and their default values in one place,
 * can override those values on running devices for debugging and testing purposes, and can control
 * what flags are available to be used on release builds.
 *
 * TODO(b/279054964): A lot. This is skeleton code right now.
 * @hide
 */
public class FeatureFlags {
    private static FeatureFlags sInstance;
    private static final Object sInstanceLock = new Object();

    private final Set<ChangeListener> mListeners = new HashSet<>();

    /**
     * Obtain a per-process instance of FeatureFlags.
     * @return
     */
    @NonNull
    public static FeatureFlags getInstance() {
        synchronized (sInstanceLock) {
            if (sInstance == null) {
                sInstance = new FeatureFlags();
            }
        }

        return sInstance;
    }

    FeatureFlags() {
    }

    /**
     * Returns whether the supplied flag is true or not.
     *
     * {@link BooleanFlag} should only be used in debug builds. They do not get optimized out.
     *
     * The first time a flag is read, its value is cached for the lifetime of the process.
     */
    public boolean isEnabled(@NonNull BooleanFlag flag) {
        return flag.getDefault();
    }

    /**
     * Returns whether the supplied flag is true or not.
     *
     * Always returns false.
     */
    public boolean isEnabled(@NonNull FusedOffFlag flag) {
        return false;
    }

    /**
     * Returns whether the supplied flag is true or not.
     *
     * Always returns true;
     */
    public boolean isEnabled(@NonNull FusedOnFlag flag) {
        return true;
    }

    /**
     * Returns whether the supplied flag is true or not.
     *
     * Can return a different value for the flag each time it is called if an override comes in.
     */
    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
        return flag.getDefault();
    }

    /**
     * Add a listener to be alerted when a {@link DynamicFlag} changes.
     *
     * See also {@link #removeChangeListener(ChangeListener)}.
     *
     * @param listener The listener to add.
     */
    public void addChangeListener(@NonNull ChangeListener listener) {
        mListeners.add(listener);
    }

    /**
     * Remove a listener that was added earlier.
     *
     * See also {@link #addChangeListener(ChangeListener)}.
     *
     * @param listener The listener to remove.
     */
    public void removeChangeListener(@NonNull ChangeListener listener) {
        mListeners.remove(listener);
    }

    /**
     * A simpler listener that is alerted when a {@link DynamicFlag} changes.
     *
     * See {@link #addChangeListener(ChangeListener)}
     */
    public interface ChangeListener {
        /**
         * Called when a {@link DynamicFlag} changes.
         *
         * @param flag The flag that has changed.
         */
        void onFlagChanged(DynamicFlag<?> flag);
    }
}
+49 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.flags;

import android.annotation.NonNull;

/**
 * An implementation of {@link FeatureFlags} for testing.
 * @hide
 */
public class FeatureFlagsFake extends FeatureFlags {
    public FeatureFlagsFake() {
        super();
    }

    @Override
    public boolean isEnabled(@NonNull BooleanFlag flag) {
        return flag.getDefault();
    }

    @Override
    public boolean isEnabled(@NonNull FusedOffFlag flag) {
        return false;
    }

    @Override
    public boolean isEnabled(@NonNull FusedOnFlag flag) {
        return true;
    }

    @Override
    public boolean isCurrentlyEnabled(@NonNull DynamicBooleanFlag flag) {
        return flag.getDefault();
    }
}
Loading