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

Commit 3359e080 authored by Chris Antol's avatar Chris Antol
Browse files

Improvements to Settings Preference Service

- Remove explicit start/stop for client binding
- Add javadoc for clarifications
- Support Int type of Preference
- Use Intent instead of PendingIntent

Bug: 378674489
Bug: 375193223
Flag: com.android.settingslib.flags.settings_catalyst
Test: atest CtsSettingsPreferenceServiceTest
Change-Id: I6fe7916385ab16f204b2bacd80bf94444e25f7ef
parent 81ff6088
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -42411,7 +42411,7 @@ package android.service.settings.preferences {
    method @NonNull public java.util.List<java.lang.String> getBreadcrumbs();
    method @NonNull public android.os.Bundle getExtras();
    method @NonNull public String getKey();
    method @Nullable public android.app.PendingIntent getLaunchIntent();
    method @Nullable public android.content.Intent getLaunchIntent();
    method @NonNull public java.util.List<java.lang.String> getReadPermissions();
    method @NonNull public String getScreenKey();
    method @Nullable public String getSummary();
@@ -42436,7 +42436,7 @@ package android.service.settings.preferences {
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.app.PendingIntent);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.content.Intent);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setReadPermissions(@NonNull java.util.List<java.lang.String>);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setRestricted(boolean);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setSummary(@Nullable String);
@@ -42456,19 +42456,18 @@ package android.service.settings.preferences {
  }
  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public class SettingsPreferenceServiceClient implements java.lang.AutoCloseable {
    ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String);
    ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.SettingsPreferenceServiceClient,java.lang.Exception>);
    method public void close();
    method public void getAllPreferenceMetadata(@NonNull android.service.settings.preferences.MetadataRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.MetadataResult,java.lang.Exception>);
    method public void getPreferenceValue(@NonNull android.service.settings.preferences.GetValueRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.GetValueResult,java.lang.Exception>);
    method public void setPreferenceValue(@NonNull android.service.settings.preferences.SetValueRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.SetValueResult,java.lang.Exception>);
    method public void start();
    method public void stop();
  }
  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceValue implements android.os.Parcelable {
    method public int describeContents();
    method public boolean getBooleanValue();
    method public double getDoubleValue();
    method public int getIntValue();
    method public long getLongValue();
    method @Nullable public String getStringValue();
    method public int getType();
@@ -42476,6 +42475,7 @@ package android.service.settings.preferences {
    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceValue> CREATOR;
    field public static final int TYPE_BOOLEAN = 0; // 0x0
    field public static final int TYPE_DOUBLE = 2; // 0x2
    field public static final int TYPE_INT = 4; // 0x4
    field public static final int TYPE_LONG = 1; // 0x1
    field public static final int TYPE_STRING = 3; // 0x3
  }
@@ -42485,6 +42485,7 @@ package android.service.settings.preferences {
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build();
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setDoubleValue(double);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setIntValue(int);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setLongValue(long);
    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setStringValue(@Nullable String);
  }
+1 −1
Original line number Diff line number Diff line
@@ -3283,7 +3283,7 @@ package android.service.quicksettings {
package android.service.settings.preferences {

  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public class SettingsPreferenceServiceClient implements java.lang.AutoCloseable {
    ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String, boolean, @Nullable android.content.ServiceConnection);
    ctor public SettingsPreferenceServiceClient(@NonNull android.content.Context, @NonNull String, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.service.settings.preferences.SettingsPreferenceServiceClient,java.lang.Exception>);
  }

}
+14 −8
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ package android.service.settings.preferences;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -63,7 +63,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
    private final boolean mRestricted;
    private final int mSensitivity;
    @Nullable
    private final PendingIntent mLaunchIntent;
    private final Intent mLaunchIntent;
    @NonNull
    private final Bundle mExtras;

@@ -149,6 +149,8 @@ public final class SettingsPreferenceMetadata implements Parcelable {

    /**
     * Returns whether Preference is restricted.
     * <p>If true, this means the Preference is treated as a Restricted Preference which indicates
     * that it could be conditionally disabled/unavailable due to admin settings.
     */
    public boolean isRestricted() {
        return mRestricted;
@@ -165,14 +167,18 @@ public final class SettingsPreferenceMetadata implements Parcelable {
    /**
     * Returns the intent to launch the host app page for this Preference.
     */
    @SuppressLint("IntentBuilderName")
    @Nullable
    public PendingIntent getLaunchIntent() {
    public Intent getLaunchIntent() {
        return mLaunchIntent;
    }

    /**
     * Returns any additional fields specific to this preference.
     * <p>Treat all data as optional.
     * <p>Treat all data as optional. This may contain unstructured data for a given preference,
     * where the type and format of this data may only known by inspecting the source code of that
     * preference. As such, any access of this data must handle failures gracefully to account for
     * changing or missing data.
     */
    @NonNull
    public Bundle getExtras() {
@@ -236,8 +242,8 @@ public final class SettingsPreferenceMetadata implements Parcelable {
        mWritable = in.readBoolean();
        mRestricted = in.readBoolean();
        mSensitivity = in.readInt();
        mLaunchIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
                PendingIntent.class);
        mLaunchIntent = in.readParcelable(Intent.class.getClassLoader(),
                Intent.class);
        mExtras = Objects.requireNonNullElseGet(in.readBundle(), Bundle::new);
    }

@@ -298,7 +304,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
        private boolean mWritable = false;
        private boolean mRestricted = false;
        @WriteSensitivity private int mSensitivity = INTENT_ONLY;
        private PendingIntent mLaunchIntent;
        private Intent mLaunchIntent;
        private Bundle mExtras;

        /**
@@ -411,7 +417,7 @@ public final class SettingsPreferenceMetadata implements Parcelable {
         * Sets the intent to launch the host app page for this preference.
         */
        @NonNull
        public Builder setLaunchIntent(@Nullable PendingIntent launchIntent) {
        public Builder setLaunchIntent(@Nullable Intent launchIntent) {
            mLaunchIntent = launchIntent;
            return this;
        }
+84 −63
Original line number Diff line number Diff line
@@ -49,67 +49,55 @@ import java.util.concurrent.Executor;
 * available services, a caller may query {@link android.content.pm.PackageManager} for applications
 * that provide the intent action {@link SettingsPreferenceService#ACTION_PREFERENCE_SERVICE} that
 * are also system applications ({@link android.content.pm.ApplicationInfo#FLAG_SYSTEM}).
 * <p>
 * Note: Each instance of this client will open a binding to an application. This can be resource
 * intensive and affect the health of the system. It is essential that each client instance is
 * only used when needed and the number of calls made are minimal.
 */
@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
public class SettingsPreferenceServiceClient implements AutoCloseable {

    @NonNull
    private final Context mContext;
    @NonNull
    private final Intent mServiceIntent;
    @NonNull
    private final ServiceConnection mServiceConnection;
    private final boolean mSystemOnly;
    @Nullable
    private ISettingsPreferenceService mRemoteService;

    /**
     * Construct a client for binding to a {@link SettingsPreferenceService} provided by the
     * application corresponding to the provided package name.
     * @param packageName - package name for which this client will initiate a service binding
     * @param context Application context
     * @param packageName package name for which this client will initiate a service binding
     * @param callbackExecutor executor on which to invoke clientReadyCallback
     * @param clientReadyCallback callback invoked once the client is ready, error otherwise
     */
    public SettingsPreferenceServiceClient(@NonNull Context context,
                                           @NonNull String packageName) {
        this(context, packageName, true, null);
    public SettingsPreferenceServiceClient(
            @NonNull Context context,
            @NonNull String packageName,
            @CallbackExecutor @NonNull Executor callbackExecutor,
            @NonNull
            OutcomeReceiver<SettingsPreferenceServiceClient, Exception> clientReadyCallback) {
        this(context, packageName, true, callbackExecutor, clientReadyCallback);
    }

    /**
     * @hide Only to be called directly by test
     */
    @TestApi
    public SettingsPreferenceServiceClient(@NonNull Context context,
    public SettingsPreferenceServiceClient(
            @NonNull Context context,
            @NonNull String packageName,
            boolean systemOnly,
                                           @Nullable ServiceConnection connectionListener) {
            @CallbackExecutor @NonNull Executor callbackExecutor,
            @NonNull
            OutcomeReceiver<SettingsPreferenceServiceClient, Exception> clientReadyCallback) {
        mContext = context.getApplicationContext();
        mServiceIntent = new Intent(ACTION_PREFERENCE_SERVICE).setPackage(packageName);
        mSystemOnly = systemOnly;
        mServiceConnection = createServiceConnection(connectionListener);
    }

    /**
     * Initiate binding to service.
     * <p>If no service exists for the package provided or the package is not for a system
     * application, no binding will occur.
     */
    public void start() {
        PackageManager pm = mContext.getPackageManager();
        PackageManager.ResolveInfoFlags flags;
        if (mSystemOnly) {
            flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY);
        } else {
            flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL);
        }
        List<ResolveInfo> infos = pm.queryIntentServices(mServiceIntent, flags);
        if (infos.size() == 1) {
            mContext.bindService(mServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
        }
    }

    /**
     * If there is an active service binding, unbind from that service.
     */
    public void stop() {
        if (mRemoteService != null) {
            mRemoteService = null;
            mContext.unbindService(mServiceConnection);
        }
        mServiceConnection = createServiceConnection(callbackExecutor, clientReadyCallback);
        connect(systemOnly, callbackExecutor, clientReadyCallback);
    }

    /**
@@ -209,40 +197,73 @@ public class SettingsPreferenceServiceClient implements AutoCloseable {
        }
    }

    /**
     * This client handles a resource, thus is it important to appropriately close that resource
     * when it is no longer needed.
     * <p>This method is provided by {@link AutoCloseable} and calling it
     * will unbind any service binding.
     */
    @Override
    public void close() {
        if (mRemoteService != null) {
            mRemoteService = null;
            mContext.unbindService(mServiceConnection);
        }
    }

    /*
     * Initiate binding to service.
     * <p>If no service exists for the package provided or the package is not for a system
     * application, no binding will occur.
     */
    private void connect(
            boolean matchSystemOnly,
            @NonNull Executor callbackExecutor,
            @NonNull OutcomeReceiver<SettingsPreferenceServiceClient, Exception> clientCallback) {
        PackageManager pm = mContext.getPackageManager();
        PackageManager.ResolveInfoFlags flags;
        if (matchSystemOnly) {
            flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY);
        } else {
            flags = PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL);
        }
        List<ResolveInfo> infos = pm.queryIntentServices(mServiceIntent, flags);
        if (infos.size() != 1
                || !mContext.bindService(mServiceIntent, mServiceConnection,
                Context.BIND_AUTO_CREATE)) {
            callbackExecutor.execute(() ->
                    clientCallback.onError(new IllegalStateException("Unable to bind service")));
        }
    }

    @NonNull
    private ServiceConnection createServiceConnection(@Nullable ServiceConnection listener) {
    private ServiceConnection createServiceConnection(
            @NonNull Executor callbackExecutor,
            @NonNull OutcomeReceiver<SettingsPreferenceServiceClient, Exception> clientCallback) {
        return new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                mRemoteService = getPreferenceServiceInterface(service);
                if (listener != null) {
                    listener.onServiceConnected(name, service);
                }
                mRemoteService = ISettingsPreferenceService.Stub.asInterface(service);
                callbackExecutor.execute(() ->
                        clientCallback.onResult(SettingsPreferenceServiceClient.this));
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                mRemoteService = null;
                if (listener != null) {
                    listener.onServiceDisconnected(name);
                }
            }
        };
            }

    @NonNull
    private ISettingsPreferenceService getPreferenceServiceInterface(@NonNull IBinder service) {
        return ISettingsPreferenceService.Stub.asInterface(service);
            @Override
            public void onBindingDied(ComponentName name) {
                close();
            }

    /**
     * This client handles a resource, thus is it important to appropriately close that resource
     * when it is no longer needed.
     * <p>This method is provided by {@link AutoCloseable} and calling it
     * will unbind any service binding.
     */
            @Override
    public void close() {
        stop();
            public void onNullBinding(ComponentName name) {
                callbackExecutor.execute(() -> clientCallback.onError(
                        new IllegalStateException("Unable to connect client")));
                close();
            }
        };
    }
}
+24 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ public final class SettingsPreferenceValue implements Parcelable {
    @Type
    private final int mType;
    private final boolean mBooleanValue;
    private final int mIntValue;
    private final long mLongValue;
    private final double mDoubleValue;
    @Nullable
@@ -64,6 +65,13 @@ public final class SettingsPreferenceValue implements Parcelable {
        return mBooleanValue;
    }

    /**
     * Returns the int value for Preference if type is {@link #TYPE_INT}.
     */
    public int getIntValue() {
        return mIntValue;
    }

    /**
     * Returns the long value for Preference if type is {@link #TYPE_LONG}.
     */
@@ -92,6 +100,7 @@ public final class SettingsPreferenceValue implements Parcelable {
            TYPE_LONG,
            TYPE_DOUBLE,
            TYPE_STRING,
            TYPE_INT,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface Type {}
@@ -104,6 +113,8 @@ public final class SettingsPreferenceValue implements Parcelable {
    public static final int TYPE_DOUBLE = 2;
    /** Value is of type string. Access via {@link #getStringValue}. */
    public static final int TYPE_STRING = 3;
    /** Value is of type int. Access via {@link #getIntValue}. */
    public static final int TYPE_INT = 4;

    private SettingsPreferenceValue(@NonNull Builder builder) {
        mType = builder.mType;
@@ -111,6 +122,7 @@ public final class SettingsPreferenceValue implements Parcelable {
        mLongValue = builder.mLongValue;
        mDoubleValue = builder.mDoubleValue;
        mStringValue = builder.mStringValue;
        mIntValue = builder.mIntValue;
    }

    private SettingsPreferenceValue(@NonNull Parcel in) {
@@ -119,6 +131,7 @@ public final class SettingsPreferenceValue implements Parcelable {
        mLongValue = in.readLong();
        mDoubleValue = in.readDouble();
        mStringValue = in.readString8();
        mIntValue = in.readInt();
    }

    /** @hide */
@@ -129,6 +142,7 @@ public final class SettingsPreferenceValue implements Parcelable {
        dest.writeLong(mLongValue);
        dest.writeDouble(mDoubleValue);
        dest.writeString8(mStringValue);
        dest.writeInt(mIntValue);
    }

    /** @hide */
@@ -163,6 +177,7 @@ public final class SettingsPreferenceValue implements Parcelable {
        private long mLongValue;
        private double mDoubleValue;
        private String mStringValue;
        private int mIntValue;

        /**
         * Create Builder instance.
@@ -182,6 +197,15 @@ public final class SettingsPreferenceValue implements Parcelable {
            return this;
        }

        /**
         * Sets the int value for Preference.
         */
        @NonNull
        public Builder setIntValue(int intValue) {
            mIntValue = intValue;
            return this;
        }

        /**
         * Sets long value for Preference.
         */