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

Commit 1013d298 authored by Matt Pape's avatar Matt Pape
Browse files

Add DeviceConfig.Properties and return it from change listener.

Add a second method to the OnPropertyChangedListener which returns a
Properties object instead of a single flag. This object will hold only
one flag for now (Q) but easily supports multiple flags at a time when
we are ready to support atomic writes (R). This Properties object also
contains typed getters, so we don't force every single client to
dupliate the parsing and default value logic.

For now, the new method in the change listener has a default
implementation which calls the old method. This ensures that no one's
code is broken (because they aren't implementing the new method) and no
one's feature stops working (the old method is still being called).

This is part 1 of a 4 step migration from the single flag callback to
the new Properties callback.

Part 2 will update the OneTimeDeviceConfigListener in the gts/ repo to
override both the old and new callback methods.

Part 3 will update the DeviceConfig.OnPropertyChangedListener to provide
a default implementation for the old method instead of a default
implementation for the new method. At the same time, all implementations
of DeviceConfig.OnPropertyChangedListener in the core repo will be
updated to implement the new method instead of the old method.

Part 4 will update the OneTimeDeviceConfigListener in the gts/ repo to
override only the new callback method and delete the implementation of
the old callback method.

These 4 parts have to happen in order.

Bug: 126414261
Test: atest FrameworksCoreTests:DeviceConfigTest

Change-Id: If75bce326dcdbc38e22d95982e57ad466cbecdb6
parent efed8e3d
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -5885,6 +5885,7 @@ package android.provider {
  }
  public static interface DeviceConfig.OnPropertyChangedListener {
    method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
    method public void onPropertyChanged(String, String, String);
  }
@@ -5894,6 +5895,16 @@ package android.provider {
    field public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
  }
  public static class DeviceConfig.Properties {
    method public boolean getBoolean(@NonNull String, boolean);
    method public float getFloat(@NonNull String, float);
    method public int getInt(@NonNull String, int);
    method @NonNull public java.util.Set<java.lang.String> getKeyset();
    method public long getLong(@NonNull String, long);
    method @NonNull public String getNamespace();
    method @Nullable public String getString(@NonNull String, @Nullable String);
  }
  public static interface DeviceConfig.Rollback {
    field public static final String BOOT_NAMESPACE = "rollback_boot";
    field public static final String ENABLE_ROLLBACK_TIMEOUT = "enable_rollback_timeout";
+11 −0
Original line number Diff line number Diff line
@@ -1991,6 +1991,7 @@ package android.provider {
  }

  public static interface DeviceConfig.OnPropertyChangedListener {
    method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
    method public void onPropertyChanged(String, String, String);
  }

@@ -1999,6 +2000,16 @@ package android.provider {
    field public static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = "location_access_check_enabled";
  }

  public static class DeviceConfig.Properties {
    method public boolean getBoolean(@NonNull String, boolean);
    method public float getFloat(@NonNull String, float);
    method public int getInt(@NonNull String, int);
    method @NonNull public java.util.Set<java.lang.String> getKeyset();
    method public long getLong(@NonNull String, long);
    method @NonNull public String getNamespace();
    method @Nullable public String getString(@NonNull String, @Nullable String);
  }

  public final class MediaStore {
    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
    method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
+151 −5
Original line number Diff line number Diff line
@@ -33,10 +33,12 @@ import android.provider.Settings.ResetMode;
import android.util.Pair;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;

/**
@@ -656,16 +658,18 @@ public final class DeviceConfig {
    private static void handleChange(Uri uri) {
        List<String> pathSegments = uri.getPathSegments();
        // pathSegments(0) is "config"
        String namespace = pathSegments.get(1);
        String name = pathSegments.get(2);
        String value = getProperty(namespace, name);
        final String namespace = pathSegments.get(1);
        final String name = pathSegments.get(2);
        final String value = getProperty(namespace, name);
        synchronized (sLock) {
            for (OnPropertyChangedListener listener : sListeners.keySet()) {
            for (final OnPropertyChangedListener listener : sListeners.keySet()) {
                if (namespace.equals(sListeners.get(listener).first)) {
                    sListeners.get(listener).second.execute(new Runnable() {
                        @Override
                        public void run() {
                            listener.onPropertyChanged(namespace, name, value);
                            Map<String, String> propertyMap = new HashMap(1);
                            propertyMap.put(name, value);
                            listener.onPropertiesChanged(new Properties(namespace, propertyMap));
                        }

                    });
@@ -692,5 +696,147 @@ public final class DeviceConfig {
         * @param value     The new value of the property which has changed.
         */
        void onPropertyChanged(String namespace, String name, String value);

        /**
         * Called when one or more properties have changed.
         *
         * @param properties Contains the complete collection of properties which have changed for a
         *         single namespace.
         */
        default void onPropertiesChanged(@NonNull Properties properties) {
            // During the transitional period, this method calls the old one to ensure legacy
            // callers continue to function as expected. Ignore this if you are implementing it for
            // yourself.
            String namespace = properties.getNamespace();
            for (String name : properties.getKeyset()) {
                onPropertyChanged(namespace, name, properties.getString(name, null));
            }
        }
    }

    /**
     * A mapping of properties to values, as well as a single namespace which they all belong to.
     *
     * @hide
     */
    @SystemApi
    @TestApi
    public static class Properties {
        private final String mNamespace;
        private final HashMap<String, String> mMap;

        /**
         * Create a mapping of properties to values and the namespace they belong to.
         *
         * @param namespace The namespace these properties belong to.
         * @param keyValueMap A map between property names and property values.
         */
        Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
            Preconditions.checkNotNull(namespace);
            mNamespace = namespace;
            mMap = new HashMap();
            if (keyValueMap != null) {
                mMap.putAll(keyValueMap);
            }
        }

        /**
         * @return the namespace all properties within this instance belong to.
         */
        @NonNull
        public String getNamespace() {
            return mNamespace;
        }

        /**
         * @return the non-null set of property names.
         */
        @NonNull
        public Set<String> getKeyset() {
            return mMap.keySet();
        }

        /**
         * Look up the String value of a property.
         *
         * @param name         The name of the property to look up.
         * @param defaultValue The value to return if the property has not been defined.
         * @return the corresponding value, or defaultValue if none exists.
         */
        @Nullable
        public String getString(@NonNull String name, @Nullable String defaultValue) {
            Preconditions.checkNotNull(name);
            String value = mMap.get(name);
            return value != null ? value : defaultValue;
        }

        /**
         * Look up the boolean value of a property.
         *
         * @param name         The name of the property to look up.
         * @param defaultValue The value to return if the property has not been defined.
         * @return the corresponding value, or defaultValue if none exists.
         */
        public boolean getBoolean(@NonNull String name, boolean defaultValue) {
            Preconditions.checkNotNull(name);
            String value = mMap.get(name);
            return value != null ? Boolean.parseBoolean(value) : defaultValue;
        }

        /**
         * Look up the int value of a property.
         *
         * @param name         The name of the property to look up.
         * @param defaultValue The value to return if the property has not been defined or fails to
         *                     parse into an int.
         * @return the corresponding value, or defaultValue if no valid int is available.
         */
        public int getInt(@NonNull String name, int defaultValue) {
            Preconditions.checkNotNull(name);
            String value = mMap.get(name);
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        }

        /**
         * Look up the long value of a property.
         *
         * @param name         The name of the property to look up.
         * @param defaultValue The value to return if the property has not been defined. or fails to
         *                     parse into a long.
         * @return the corresponding value, or defaultValue if no valid long is available.
         */
        public long getLong(@NonNull String name, long defaultValue) {
            Preconditions.checkNotNull(name);
            String value = mMap.get(name);
            try {
                return Long.parseLong(value);
            } catch (NumberFormatException e) {
                return defaultValue;
            }
        }

        /**
         * Look up the int value of a property.
         *
         * @param name         The name of the property to look up.
         * @param defaultValue The value to return if the property has not been defined. or fails to
         *                     parse into a float.
         * @return the corresponding value, or defaultValue if no valid float is available.
         */
        public float getFloat(@NonNull String name, float defaultValue) {
            Preconditions.checkNotNull(name);
            String value = mMap.get(name);
            try {
                return Float.parseFloat(value);
            } catch (NumberFormatException e) {
                return defaultValue;
            } catch (NullPointerException e) {
                return defaultValue;
            }
        }
    }
}
+32 −2
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

@@ -223,7 +224,29 @@ public class DeviceConfigTest {
    }

    @Test
    public void testListener() throws InterruptedException {
    public void testListener_propertiesCallback() throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(1);

        OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
            public void onPropertyChanged(String namespace, String name, String value) {
                // ignore legacy callback
            }

            @Override
            public void onPropertiesChanged(DeviceConfig.Properties properties) {
                assertThat(properties.getNamespace()).isEqualTo(sNamespace);
                assertThat(properties.getKeyset().size()).isEqualTo(1);
                assertThat(properties.getKeyset()).contains(sKey);
                assertThat(properties.getString(sKey, "default_value")).isEqualTo(sValue);
                countDownLatch.countDown();
            }
        };

        testListener(countDownLatch, changeListener);
    }

    @Test
    public void testListener_legacyCallback() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);

        OnPropertyChangedListener changeListener = (namespace, name, value) -> {
@@ -233,16 +256,23 @@ public class DeviceConfigTest {
            countDownLatch.countDown();
        };

        testListener(countDownLatch, changeListener);

    }

    private void testListener(CountDownLatch countDownLatch,
            OnPropertyChangedListener changeListener) {
        try {
            DeviceConfig.addOnPropertyChangedListener(sNamespace,
                    ActivityThread.currentApplication().getMainExecutor(), changeListener);
            DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
            assertThat(countDownLatch.await(
                    WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
        } catch (InterruptedException e) {
            Assert.fail(e.getMessage());
        } finally {
            DeviceConfig.removeOnPropertyChangedListener(changeListener);
        }

    }

    private static boolean deleteViaContentProvider(String namespace, String key) {