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

Commit 9f2cd101 authored by Reema Bajwa's avatar Reema Bajwa Committed by Android (Google) Code Review
Browse files

Merge "Add multiple services support in SecureSettingsServiceNameResolver, to...

Merge "Add multiple services support in SecureSettingsServiceNameResolver, to be used by CredentialManagerService Test: Built and deployed locally Bug: 250713478"
parents 38cde617 cdf6e228
Loading
Loading
Loading
Loading
+8 −271
Original line number Diff line number Diff line
@@ -19,28 +19,9 @@ import android.annotation.ArrayRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Gets the service name using a framework resources, temporarily changing the service if necessary
@@ -48,259 +29,42 @@ import java.util.List;
 *
 * @hide
 */
public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {

    private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {

    /** Handler message to {@link #resetTemporaryService(int)} */
    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;

    @NonNull
    private final Context mContext;
    @NonNull
    private final Object mLock = new Object();
    @StringRes
    private final int mStringResourceId;
    @ArrayRes
    private final int mArrayResourceId;
    private final boolean mIsMultiple;
    /**
     * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
     * keyed by {@code userId}.
     *
     * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
     * mIsMultiple is true.
     */
    @GuardedBy("mLock")
    private final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
    /**
     * Map of default services that have been disabled by
     * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
     *
     * <p>Typically used by Shell command and/or CTS tests.
     */
    @GuardedBy("mLock")
    private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
    @Nullable
    private NameResolverListener mOnSetCallback;
    /**
     * When the temporary service will expire (and reset back to the default).
     */
    @GuardedBy("mLock")
    private long mTemporaryServiceExpiration;

    /**
     * Handler used to reset the temporary service name.
     */
    @GuardedBy("mLock")
    private Handler mTemporaryHandler;

    public FrameworkResourcesServiceNameResolver(@NonNull Context context,
            @StringRes int resourceId) {
        mContext = context;
        super(context, false);
        mStringResourceId = resourceId;
        mArrayResourceId = -1;
        mIsMultiple = false;
    }

    public FrameworkResourcesServiceNameResolver(@NonNull Context context,
            @ArrayRes int resourceId, boolean isMultiple) {
        super(context, isMultiple);
        if (!isMultiple) {
            throw new UnsupportedOperationException("Please use "
                    + "FrameworkResourcesServiceNameResolver(context, @StringRes int) constructor "
                    + "if single service mode is requested.");
        }
        mContext = context;
        mStringResourceId = -1;
        mArrayResourceId = resourceId;
        mIsMultiple = true;
    }

    @Override
    public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
        synchronized (mLock) {
            this.mOnSetCallback = callback;
        }
    }

    @Override
    public String getServiceName(@UserIdInt int userId) {
        String[] serviceNames = getServiceNameList(userId);
        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
    }

    @Override
    public String getDefaultServiceName(@UserIdInt int userId) {
        String[] serviceNames = getDefaultServiceNameList(userId);
        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
    }

    /**
     * Gets the default list of the service names for the given user.
     *
     * <p>Typically implemented by services which want to provide multiple backends.
     */
    @Override
    public String[] getServiceNameList(int userId) {
        synchronized (mLock) {
            String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
            if (temporaryNames != null) {
                // Always log it, as it should only be used on CTS or during development
                Slog.w(TAG, "getServiceName(): using temporary name "
                        + Arrays.toString(temporaryNames) + " for user " + userId);
                return temporaryNames;
            }
            final boolean disabled = mDefaultServicesDisabled.get(userId);
            if (disabled) {
                // Always log it, as it should only be used on CTS or during development
                Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
                        + "user " + userId);
                return null;
            }
            return getDefaultServiceNameList(userId);

        }
    }

    /**
     * Gets the default list of the service names for the given user.
     *
     * <p>Typically implemented by services which want to provide multiple backends.
     */
    @Override
    public String[] getDefaultServiceNameList(int userId) {
        synchronized (mLock) {
            if (mIsMultiple) {
                String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId);
                // Filter out unimplemented services
                // Initialize the validated array as null because we do not know the final size.
                List<String> validatedServiceNameList = new ArrayList<>();
                try {
                    for (int i = 0; i < serviceNameList.length; i++) {
                        if (TextUtils.isEmpty(serviceNameList[i])) {
                            continue;
                        }
                        ComponentName serviceComponent = ComponentName.unflattenFromString(
                                serviceNameList[i]);
                        ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
                                serviceComponent,
                                PackageManager.MATCH_DIRECT_BOOT_AWARE
                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
                        if (serviceInfo != null) {
                            validatedServiceNameList.add(serviceNameList[i]);
                        }
                    }
                } catch (Exception e) {
                    Slog.e(TAG, "Could not validate provided services.", e);
                }
                String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
                return validatedServiceNameList.toArray(validatedServiceNameArray);
            } else {
                final String name = mContext.getString(mStringResourceId);
                return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
            }
        }
    }

    @Override
    public boolean isConfiguredInMultipleMode() {
        return mIsMultiple;
    }

    @Override
    public boolean isTemporary(@UserIdInt int userId) {
        synchronized (mLock) {
            return mTemporaryServiceNamesList.get(userId) != null;
        }
    }

    @Override
    public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
            int durationMs) {
        setTemporaryServices(userId, new String[]{componentName}, durationMs);
    }

    @Override
    public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
        synchronized (mLock) {
            mTemporaryServiceNamesList.put(userId, componentNames);

            if (mTemporaryHandler == null) {
                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
                            synchronized (mLock) {
                                resetTemporaryService(userId);
                            }
                        } else {
                            Slog.wtf(TAG, "invalid handler msg: " + msg);
                        }
                    }
                };
            } else {
                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
            }
            mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
            mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
            for (int i = 0; i < componentNames.length; i++) {
                notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
                        /* isTemporary= */ true);
            }
        }
    }

    @Override
    public void resetTemporaryService(@UserIdInt int userId) {
        synchronized (mLock) {
            Slog.i(TAG, "resetting temporary service for user " + userId + " from "
                    + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
            mTemporaryServiceNamesList.remove(userId);
            if (mTemporaryHandler != null) {
                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
                mTemporaryHandler = null;
            }
            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
                    /* isTemporary= */ false);
        }
    }

    @Override
    public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
        synchronized (mLock) {
            final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
            if (currentlyEnabled == enabled) {
                Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
                return false;
            }
            if (enabled) {
                Slog.i(TAG, "disabling default service for user " + userId);
                mDefaultServicesDisabled.removeAt(userId);
            } else {
                Slog.i(TAG, "enabling default service for user " + userId);
                mDefaultServicesDisabled.put(userId, true);
            }
        }
        return true;
    public String[] readServiceNameList(int userId) {
        return mContext.getResources().getStringArray(mArrayResourceId);
    }

    @Nullable
    @Override
    public boolean isDefaultServiceEnabled(int userId) {
        synchronized (mLock) {
            return isDefaultServiceEnabledLocked(userId);
        }
    public String readServiceName(int userId) {
        return mContext.getResources().getString(mStringResourceId);
    }

    private boolean isDefaultServiceEnabledLocked(int userId) {
        return !mDefaultServicesDisabled.get(userId);
    }

    @Override
    public String toString() {
        synchronized (mLock) {
            return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
        }
    }

    // TODO(b/117779333): support proto
    @Override
@@ -314,31 +78,4 @@ public final class FrameworkResourcesServiceNameResolver implements ServiceNameR
            pw.print(mDefaultServicesDisabled.size());
        }
    }

    // TODO(b/117779333): support proto
    @Override
    public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
        synchronized (mLock) {
            final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
            if (temporaryNames != null) {
                pw.print("tmpName=");
                pw.print(Arrays.toString(temporaryNames));
                final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
                pw.print(" (expires in ");
                TimeUtils.formatDuration(ttl, pw);
                pw.print("), ");
            }
            pw.print("defaultName=");
            pw.print(getDefaultServiceName(userId));
            final boolean disabled = mDefaultServicesDisabled.get(userId);
            pw.println(disabled ? " (disabled)" : " (enabled)");
        }
    }

    private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
            @Nullable String newTemporaryName, boolean isTemporary) {
        if (mOnSetCallback != null) {
            mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
        }
    }
}
+53 −7
Original line number Diff line number Diff line
@@ -19,8 +19,11 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.Context;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;

import java.io.PrintWriter;
import java.util.Set;

/**
 * Gets the service name using a property from the {@link android.provider.Settings.Secure}
@@ -28,21 +31,34 @@ import java.io.PrintWriter;
 *
 * @hide
 */
public final class SecureSettingsServiceNameResolver implements ServiceNameResolver {
public final class SecureSettingsServiceNameResolver extends ServiceNameBaseResolver {
    /**
     * The delimiter to be used to parse the secure settings string. Services must make sure
     * that this delimiter is used while adding component names to their secure setting property.
     */
    private static final char COMPONENT_NAME_SEPARATOR = ':';

    private final @NonNull Context mContext;
    private final TextUtils.SimpleStringSplitter mStringColonSplitter =
            new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);

    @NonNull
    private final String mProperty;

    public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property) {
        mContext = context;
        mProperty = property;
        this(context, property, /*isMultiple*/false);
    }

    @Override
    public String getDefaultServiceName(@UserIdInt int userId) {
        return Settings.Secure.getStringForUser(mContext.getContentResolver(), mProperty, userId);
    /**
     *
     * @param context the context required to retrieve the secure setting value
     * @param property name of the secure setting key
     * @param isMultiple true if the system service using this resolver needs to connect to
     *                   multiple remote services, false otherwise
     */
    public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property,
            boolean isMultiple) {
        super(context, isMultiple);
        mProperty = property;
    }

    // TODO(b/117779333): support proto
@@ -61,4 +77,34 @@ public final class SecureSettingsServiceNameResolver implements ServiceNameResol
    public String toString() {
        return "SecureSettingsServiceNameResolver[" + mProperty + "]";
    }

    @Override
    public String[] readServiceNameList(int userId) {
        return parseColonDelimitedServiceNames(
                Settings.Secure.getStringForUser(
                        mContext.getContentResolver(), mProperty, userId));
    }

    @Override
    public String readServiceName(int userId) {
        return Settings.Secure.getStringForUser(
                mContext.getContentResolver(), mProperty, userId);
    }

    private String[] parseColonDelimitedServiceNames(String serviceNames) {
        final Set<String> delimitedServices = new ArraySet<>();
        if (!TextUtils.isEmpty(serviceNames)) {
            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
            splitter.setString(serviceNames);
            while (splitter.hasNext()) {
                final String str = splitter.next();
                if (TextUtils.isEmpty(str)) {
                    continue;
                }
                delimitedServices.add(str);
            }
        }
        String[] delimitedServicesArray = new String[delimitedServices.size()];
        return delimitedServices.toArray(delimitedServicesArray);
    }
}
+325 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -49,7 +49,8 @@ public final class CredentialManagerService extends

    public CredentialManagerService(@NonNull Context context) {
        super(context,
                new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
                new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE,
                        /*isMultiple=*/true),
                null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
    }