Revert (with changes) "Revert "Add multiple services support in SecureSettingsServiceNameResolver, to""

This reverts commit c66070ce35.

Reason for revert: Fixing the original issue. Credential Manager Service was configured as multiple mode without implementing newServiceListLocked(). For now, setting multiple mode config to false, and will enable it with the functionality later.

Change-Id: I6cced5f917633a9b953546622493dab713c575c2
This commit is contained in:
Reema Bajwa
2022-10-24 19:49:58 +00:00
parent 3e7dbafe63
commit b9328f9689
3 changed files with 386 additions and 278 deletions

View File

@@ -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 {
public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {
private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
/** 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;
}
public String[] readServiceNameList(int userId) {
return mContext.getResources().getStringArray(mArrayResourceId);
}
@Nullable
@Override
public String getServiceName(@UserIdInt int userId) {
String[] serviceNames = getServiceNameList(userId);
return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
public String readServiceName(int userId) {
return mContext.getResources().getString(mStringResourceId);
}
@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;
}
@Override
public boolean isDefaultServiceEnabled(int userId) {
synchronized (mLock) {
return isDefaultServiceEnabledLocked(userId);
}
}
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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,325 @@
/*
* Copyright (C) 2018 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 com.android.server.infra;
import android.annotation.NonNull;
import android.annotation.Nullable;
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
* (typically during CTS tests or service development).
*
* @hide
*/
public abstract class ServiceNameBaseResolver implements ServiceNameResolver {
private static final String TAG = ServiceNameBaseResolver.class.getSimpleName();
/** Handler message to {@link #resetTemporaryService(int)} */
private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
@NonNull
protected final Context mContext;
@NonNull
protected final Object mLock = new Object();
protected 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")
protected 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")
protected 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;
protected ServiceNameBaseResolver(Context context, boolean isMultiple) {
mContext = context;
mIsMultiple = isMultiple;
}
@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);
}
}
/**
* Base classes must override this to read from the desired config e.g. framework resource,
* secure settings etc.
*/
@Nullable
public abstract String[] readServiceNameList(int userId);
/**
* Base classes must override this to read from the desired config e.g. framework resource,
* secure settings etc.
*/
@Nullable
public abstract String readServiceName(int 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 = readServiceNameList(userId);
// 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 = readServiceName(userId);
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;
}
@Override
public boolean isDefaultServiceEnabled(int userId) {
synchronized (mLock) {
return isDefaultServiceEnabledLocked(userId);
}
}
@GuardedBy("mLock")
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
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);
}
}
}