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

Commit 5627b239 authored by Chris Antol's avatar Chris Antol Committed by Android (Google) Code Review
Browse files

Merge "Improvements to Settings Preference Service" into main

parents b58bcfdc 3359e080
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -42463,7 +42463,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();
@@ -42488,7 +42488,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);
@@ -42508,19 +42508,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();
@@ -42528,6 +42527,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
  }
@@ -42537,6 +42537,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
@@ -3295,7 +3295,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.
         */