Fixed ContentCapture and AugmentedAutofill methods that should not hold the main lock...
...otherwise they could ANR the ActivityManagerService and crash the system
Test: manual verification
Test: atest AugmentedLoginActivityTest
Test: atest CtsContentCaptureServiceTestCases # sanity check (minus usual flakiness)
Fixes: 129410913
Bug: 126266412
Change-Id: I4b0b0c389dd2c34928c6fffeec2854518a5e7da1
diff --git a/core/java/com/android/internal/infra/GlobalWhitelistState.java b/core/java/com/android/internal/infra/GlobalWhitelistState.java
new file mode 100644
index 0000000..dfa59b7
--- /dev/null
+++ b/core/java/com/android/internal/infra/GlobalWhitelistState.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 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.internal.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.util.ArraySet;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Helper class used to manage a {@link WhitelistHelper} per user instance when the main service
+ * cannot hold a lock when external entities (typically {@code ActivityManagerService}) needs to
+ * get whitelist info.
+ *
+ * <p>This class is thread safe.
+ */
+public class GlobalWhitelistState {
+
+ // Uses full-name to avoid collision with service-provided mLock
+ protected final Object mGlobalWhitelistStateLock = new Object();
+
+ @Nullable
+ @GuardedBy("mGlobalWhitelistStateLock")
+ protected SparseArray<WhitelistHelper> mWhitelisterHelpers;
+
+ /**
+ * Sets the whitelist for the given user.
+ */
+ public void setWhitelist(@UserIdInt int userId, @Nullable List<String> packageNames,
+ @Nullable List<ComponentName> components) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) {
+ mWhitelisterHelpers = new SparseArray<>(1);
+ }
+ WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+ if (helper == null) {
+ helper = new WhitelistHelper();
+ mWhitelisterHelpers.put(userId, helper);
+ }
+ helper.setWhitelist(packageNames, components);
+ }
+ }
+
+ /**
+ * Checks if the given package is whitelisted for the given user.
+ */
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) return false;
+ final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+ return helper == null ? false : helper.isWhitelisted(packageName);
+ }
+ }
+
+ /**
+ * Checks if the given component is whitelisted for the given user.
+ */
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) return false;
+ final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+ return helper == null ? false : helper.isWhitelisted(componentName);
+ }
+ }
+
+ /**
+ * Gets the whitelisted components for the given package and user.
+ */
+ public ArraySet<ComponentName> getWhitelistedComponents(@UserIdInt int userId,
+ @NonNull String packageName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) return null;
+ final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+ return helper == null ? null : helper.getWhitelistedComponents(packageName);
+ }
+ }
+
+ /**
+ * Resets the whitelist for the given user.
+ */
+ public void resetWhitelist(@NonNull int userId) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) return;
+ mWhitelisterHelpers.remove(userId);
+ if (mWhitelisterHelpers.size() == 0) {
+ mWhitelisterHelpers = null;
+ }
+ }
+ }
+
+ /**
+ * Dumps it!
+ */
+ public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ pw.print(prefix); pw.print("State: ");
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) {
+ pw.println("empty");
+ return;
+ }
+ pw.print(mWhitelisterHelpers.size()); pw.println(" services");
+ final String prefix2 = prefix + " ";
+ for (int i = 0; i < mWhitelisterHelpers.size(); i++) {
+ final int userId = mWhitelisterHelpers.keyAt(i);
+ final WhitelistHelper helper = mWhitelisterHelpers.valueAt(i);
+ helper.dump(prefix2, "Whitelist for userId " + userId, pw);
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/infra/WhitelistHelper.java b/core/java/com/android/internal/infra/WhitelistHelper.java
index 183b465..d7753db 100644
--- a/core/java/com/android/internal/infra/WhitelistHelper.java
+++ b/core/java/com/android/internal/infra/WhitelistHelper.java
@@ -31,6 +31,7 @@
/**
* Helper class for keeping track of whitelisted packages/activities.
*
+ * <p><b>NOTE: </b>this class is not thread safe.
* @hide
*/
public final class WhitelistHelper {
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 7020e7e..fdc3567 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -42,6 +42,7 @@
import android.database.ContentObserver;
import android.graphics.Rect;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
@@ -61,6 +62,7 @@
import android.util.LocalLog;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillManager.SmartSuggestionMode;
@@ -72,6 +74,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.GlobalWhitelistState;
+import com.android.internal.infra.WhitelistHelper;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -146,6 +150,7 @@
private final LocalLog mWtfHistory = new LocalLog(50);
private final AutofillCompatState mAutofillCompatState = new AutofillCompatState();
+
private final LocalService mLocalService = new LocalService();
private final ActivityManagerInternal mAm;
@@ -178,6 +183,8 @@
@GuardedBy("mLock")
int mAugmentedServiceRequestTimeoutMs;
+ final AugmentedAutofillState mAugmentedAutofillState = new AugmentedAutofillState();
+
public AutofillManagerService(Context context) {
super(context,
new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
@@ -187,7 +194,7 @@
DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_AUTOFILL,
ActivityThread.currentApplication().getMainExecutor(),
- (namespace, key, value) -> onDeviceConfigChange(key, value));
+ (namespace, key, value) -> onDeviceConfigChange(key));
setLogLevelFromSettings();
setMaxPartitionsFromSettings();
@@ -201,15 +208,20 @@
mAugmentedAutofillResolver = new FrameworkResourcesServiceNameResolver(getContext(),
com.android.internal.R.string.config_defaultAugmentedAutofillService);
mAugmentedAutofillResolver.setOnTemporaryServiceNameChangedCallback(
- (u, s) -> getServiceForUserLocked(u).updateRemoteAugmentedAutofillService());
+ (u, s, t) -> onAugmentedServiceNameChanged(u, s, t));
if (mSupportedSmartSuggestionModes != AutofillManager.FLAG_SMART_SUGGESTION_OFF) {
- // Must eager load the services so they bind to the augmented autofill service
final UserManager um = getContext().getSystemService(UserManager.class);
final List<UserInfo> users = um.getUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
+ // Must eager load the services so they bind to the augmented autofill service
getServiceForUserLocked(userId);
+
+ // And also set the global state
+ mAugmentedAutofillState.setServiceInfo(userId,
+ mAugmentedAutofillResolver.getServiceName(userId),
+ mAugmentedAutofillResolver.isTemporary(userId));
}
}
}
@@ -258,7 +270,7 @@
}
}
- private void onDeviceConfigChange(@NonNull String key, @Nullable String value) {
+ private void onDeviceConfigChange(@NonNull String key) {
switch (key) {
case AutofillManager.DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES:
case AutofillManager.DEVICE_CONFIG_AUGMENTED_SERVICE_IDLE_UNBIND_TIMEOUT:
@@ -270,6 +282,14 @@
}
}
+ private void onAugmentedServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
+ boolean isTemporary) {
+ mAugmentedAutofillState.setServiceInfo(userId, serviceName, isTemporary);
+ synchronized (mLock) {
+ getServiceForUserLocked(userId).updateRemoteAugmentedAutofillService();
+ }
+ }
+
@Override // from AbstractMasterSystemService
protected AutofillManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
boolean disabled) {
@@ -783,15 +803,7 @@
final boolean compatModeEnabled = mAutofillCompatState.isCompatibilityModeRequested(
packageName, versionCode, userId);
final AutofillOptions options = new AutofillOptions(loggingLevel, compatModeEnabled);
-
- synchronized (mLock) {
- final AutofillManagerServiceImpl service =
- getServiceForUserLocked(UserHandle.getCallingUserId());
- if (service != null) {
- service.setAugmentedAutofillWhitelistLocked(options, packageName);
- }
- }
-
+ mAugmentedAutofillState.injectAugmentedAutofillInfo(options, userId, packageName);
return options;
}
}
@@ -934,6 +946,89 @@
}
}
+ /**
+ * Augmented autofill metadata associated with all services.
+ *
+ * <p>This object is defined here instead of on each {@link AutofillManagerServiceImpl} because
+ * it cannot hold a lock on the main lock when
+ * {@link AugmentedAutofillState#injectAugmentedAutofillInfo(AutofillOptions, int, String)}
+ * is called by external services.
+ */
+ static final class AugmentedAutofillState extends GlobalWhitelistState {
+
+ @GuardedBy("mGlobalWhitelistStateLock")
+ private final SparseArray<String> mServicePackages = new SparseArray<>();
+ @GuardedBy("mGlobalWhitelistStateLock")
+ private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray();
+
+ private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName,
+ boolean isTemporary) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (isTemporary) {
+ mTemporaryServices.put(userId, true);
+ } else {
+ mTemporaryServices.delete(userId);
+ }
+ if (serviceName != null) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(serviceName);
+ if (componentName == null) {
+ Slog.w(TAG, "setServiceInfo(): invalid name: " + serviceName);
+ mServicePackages.remove(userId);
+ } else {
+ mServicePackages.put(userId, componentName.getPackageName());
+ }
+ } else {
+ mServicePackages.remove(userId);
+ }
+ }
+ }
+
+ public void injectAugmentedAutofillInfo(@NonNull AutofillOptions options,
+ @UserIdInt int userId, @NonNull String packageName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mWhitelisterHelpers == null) return;
+ final WhitelistHelper helper = mWhitelisterHelpers.get(userId);
+ if (helper != null) {
+ options.augmentedAutofillEnabled = helper.isWhitelisted(packageName);
+ options.whitelistedActivitiesForAugmentedAutofill = helper
+ .getWhitelistedComponents(packageName);
+ }
+ }
+ }
+
+ @Override
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (!super.isWhitelisted(userId, componentName)) return false;
+
+ if (Build.IS_USER && mTemporaryServices.get(userId)) {
+ final String packageName = componentName.getPackageName();
+ if (!packageName.equals(mServicePackages.get(userId))) {
+ Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill "
+ + "while using temporary service " + mServicePackages.get(userId));
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ super.dump(prefix, pw);
+
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mServicePackages.size() > 0) {
+ pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages);
+ }
+ if (mTemporaryServices.size() > 0) {
+ pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices);
+ }
+ }
+ }
+ }
+
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
public void addClient(IAutoFillManagerClient client, ComponentName componentName,
@@ -1370,6 +1465,8 @@
pw.println(); pw.println("WTF history:"); pw.println();
mWtfHistory.reverseDump(fd, pw, args);
}
+ pw.println("Augmented Autofill State: ");
+ mAugmentedAutofillState.dump(prefix, pw);
}
} finally {
sDebug = realDebug;
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index fe540bf..4bd6fbd 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -31,7 +31,6 @@
import android.app.ActivityManagerInternal;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
-import android.content.AutofillOptions;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -40,7 +39,6 @@
import android.metrics.LogMaker;
import android.os.AsyncTask;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -76,7 +74,6 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.infra.WhitelistHelper;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.LocalServices;
@@ -170,12 +167,6 @@
@Nullable
private ServiceInfo mRemoteAugmentedAutofillServiceInfo;
- /**
- * List of packages/activities that are whitelisted to be trigger augmented autofill.
- */
- @GuardedBy("mLock")
- private final WhitelistHelper mAugmentedWhitelistHelper = new WhitelistHelper();
-
AutofillManagerServiceImpl(AutofillManagerService master, Object lock,
LocalLog uiLatencyHistory, LocalLog wtfHistory, int userId, AutoFillUI ui,
AutofillCompatState autofillCompatState,
@@ -951,8 +942,6 @@
pw.println(mRemoteAugmentedAutofillServiceInfo);
}
- mAugmentedWhitelistHelper.dump(prefix, "Augmented autofill whitelist", pw);
-
pw.print(prefix); pw.print("Field classification enabled: ");
pw.println(isFieldClassificationEnabledLocked());
pw.print(prefix); pw.print("Compat pkgs: ");
@@ -1234,27 +1223,7 @@
@GuardedBy("mLock")
boolean isWhitelistedForAugmentedAutofillLocked(@NonNull ComponentName componentName) {
- if (Build.IS_USER && mMaster.mAugmentedAutofillResolver.isTemporary(mUserId)) {
- final String serviceName = mMaster.mAugmentedAutofillResolver.getServiceName(mUserId);
- final ComponentName component = ComponentName.unflattenFromString(serviceName);
- final String servicePackage = component == null ? null : component.getPackageName();
- final String packageName = componentName.getPackageName();
- if (!packageName.equals(servicePackage)) {
- Slog.w(TAG, "Ignoring package " + packageName + " for augmented autofill while "
- + "using temporary service " + servicePackage);
- return false;
- }
- }
-
- return mAugmentedWhitelistHelper.isWhitelisted(componentName);
- }
-
- @GuardedBy("mLock")
- void setAugmentedAutofillWhitelistLocked(@NonNull AutofillOptions options,
- @NonNull String packageName) {
- options.augmentedAutofillEnabled = mAugmentedWhitelistHelper.isWhitelisted(packageName);
- options.whitelistedActivitiesForAugmentedAutofill = mAugmentedWhitelistHelper
- .getWhitelistedComponents(packageName);
+ return mMaster.mAugmentedAutofillState.isWhitelisted(mUserId, componentName);
}
/**
@@ -1268,7 +1237,7 @@
if (mMaster.verbose) {
Slog.v(TAG, "whitelisting packages: " + packages + "and activities: " + components);
}
- mAugmentedWhitelistHelper.setWhitelist(packages, components);
+ mMaster.mAugmentedAutofillState.setWhitelist(mUserId, packages, components);
}
}
@@ -1280,7 +1249,7 @@
if (mMaster.verbose) {
Slog.v(TAG, "resetting augmented autofill whitelist");
}
- whitelistForAugmentedAutofillPackages(null, null);
+ mMaster.mAugmentedAutofillState.resetWhitelist(mUserId);
}
private void sendStateToClients(boolean resetClient) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index b2760e0..4a230e7 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -40,6 +40,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -50,8 +51,10 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.contentcapture.ContentCaptureHelper;
import android.view.contentcapture.ContentCaptureManager;
@@ -60,6 +63,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.AbstractRemoteService;
+import com.android.internal.infra.GlobalWhitelistState;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -117,6 +121,9 @@
@GuardedBy("mLock") int mDevCfgLogHistorySize;
@GuardedBy("mLock") int mDevCfgIdleUnbindTimeoutMs;
+ final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
+ new GlobalContentCaptureOptions();
+
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
@@ -136,12 +143,12 @@
mRequestsHistory = null;
}
- // Sets which services are disabled by settings
final UserManager um = getContext().getSystemService(UserManager.class);
final List<UserInfo> users = um.getUsers();
for (int i = 0; i < users.size(); i++) {
final int userId = users.get(i).id;
final boolean disabled = !isEnabledBySettings(userId);
+ // Sets which services are disabled by settings
if (disabled) {
Slog.i(mTag, "user " + userId + " disabled by settings");
if (mDisabledBySettings == null) {
@@ -149,6 +156,10 @@
}
mDisabledBySettings.put(userId, true);
}
+ // Sets the global options for the service.
+ mGlobalContentCaptureOptions.setServiceInfo(userId,
+ mServiceNameResolver.getServiceName(userId),
+ mServiceNameResolver.isTemporary(userId));
}
}
@@ -188,6 +199,14 @@
}
@Override // from AbstractMasterSystemService
+ protected void onServiceNameChanged(@UserIdInt int userId, @NonNull String serviceName,
+ boolean isTemporary) {
+ mGlobalContentCaptureOptions.setServiceInfo(userId, serviceName, isTemporary);
+
+ super.onServiceNameChanged(userId, serviceName, isTemporary);
+ }
+
+ @Override // from AbstractMasterSystemService
protected void enforceCallingPermissionForManagement() {
getContext().enforceCallingPermission(MANAGE_CONTENT_CAPTURE, mTag);
}
@@ -496,6 +515,8 @@
pw.print(prefix2); pw.print("logHistorySize: "); pw.println(mDevCfgLogHistorySize);
pw.print(prefix2); pw.print("idleUnbindTimeoutMs: ");
pw.println(mDevCfgIdleUnbindTimeoutMs);
+ pw.print(prefix); pw.println("Global Options:");
+ mGlobalContentCaptureOptions.dump(prefix2, pw);
}
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@@ -670,13 +691,7 @@
@Override
public ContentCaptureOptions getOptionsForPackage(int userId, @NonNull String packageName) {
- synchronized (mLock) {
- final ContentCapturePerUserService service = peekServiceForUserLocked(userId);
- if (service != null) {
- return service.getOptionsForPackageLocked(packageName);
- }
- }
- return null;
+ return mGlobalContentCaptureOptions.getOptions(userId, packageName);
}
@Override
@@ -690,4 +705,92 @@
}
}
}
+
+ /**
+ * Content capture options associated with all services.
+ *
+ * <p>This object is defined here instead of on each {@link ContentCapturePerUserService}
+ * because it cannot hold a lock on the main lock when
+ * {@link GlobalContentCaptureOptions#getOptions(int, String)} is called by external services.
+ */
+ final class GlobalContentCaptureOptions extends GlobalWhitelistState {
+
+ @GuardedBy("mGlobalWhitelistStateLock")
+ private final SparseArray<String> mServicePackages = new SparseArray<>();
+ @GuardedBy("mGlobalWhitelistStateLock")
+ private final SparseBooleanArray mTemporaryServices = new SparseBooleanArray();
+
+ private void setServiceInfo(@UserIdInt int userId, @Nullable String serviceName,
+ boolean isTemporary) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (isTemporary) {
+ mTemporaryServices.put(userId, true);
+ } else {
+ mTemporaryServices.delete(userId);
+ }
+ if (serviceName != null) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(serviceName);
+ if (componentName == null) {
+ Slog.w(mTag, "setServiceInfo(): invalid name: " + serviceName);
+ mServicePackages.remove(userId);
+ } else {
+ mServicePackages.put(userId, componentName.getPackageName());
+ }
+ } else {
+ mServicePackages.remove(userId);
+ }
+ }
+ }
+
+ @Nullable
+ @GuardedBy("mGlobalWhitelistStateLock")
+ public ContentCaptureOptions getOptions(@UserIdInt int userId,
+ @NonNull String packageName) {
+ synchronized (mGlobalWhitelistStateLock) {
+ if (!isWhitelisted(userId, packageName)) {
+ if (packageName.equals(mServicePackages.get(userId))) {
+ if (verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName);
+ return new ContentCaptureOptions(mDevCfgLoggingLevel);
+ }
+ if (verbose) {
+ Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted");
+ }
+ return null;
+ }
+
+ final ArraySet<ComponentName> whitelistedComponents =
+ getWhitelistedComponents(userId, packageName);
+ if (Build.IS_USER && mServiceNameResolver.isTemporary(userId)) {
+ if (!packageName.equals(mServicePackages.get(userId))) {
+ Slog.w(mTag, "Ignoring package " + packageName
+ + " while using temporary service " + mServicePackages.get(userId));
+ return null;
+ }
+ }
+ final ContentCaptureOptions options = new ContentCaptureOptions(mDevCfgLoggingLevel,
+ mDevCfgMaxBufferSize, mDevCfgIdleFlushingFrequencyMs,
+ mDevCfgTextChangeFlushingFrequencyMs, mDevCfgLogHistorySize,
+ whitelistedComponents);
+ if (verbose) {
+ Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options);
+ }
+ return options;
+ }
+ }
+
+ @Override
+ public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+ super.dump(prefix, pw);
+
+ synchronized (mGlobalWhitelistStateLock) {
+ if (mServicePackages.size() > 0) {
+ pw.print(prefix); pw.print("Service packages: "); pw.println(mServicePackages);
+ }
+ if (mTemporaryServices.size() > 0) {
+ pw.print(prefix); pw.print("Temp services: "); pw.println(mTemporaryServices);
+ }
+ }
+ }
+ }
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index f0c6f7e..5934344 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -34,13 +34,11 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.UserHandle;
@@ -52,7 +50,6 @@
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Slog;
import android.view.contentcapture.UserDataRemovalRequest;
@@ -238,7 +235,8 @@
final int taskId = activityPresentationInfo.taskId;
final int displayId = activityPresentationInfo.displayId;
final ComponentName componentName = activityPresentationInfo.componentName;
- final boolean whiteListed = isWhitelistedLocked(componentName);
+ final boolean whiteListed = mMaster.mGlobalContentCaptureOptions.isWhitelisted(mUserId,
+ componentName);
final ComponentName serviceComponentName = getServiceComponentName();
final boolean enabled = isEnabledLocked();
if (mMaster.mRequestsHistory != null) {
@@ -315,11 +313,6 @@
newSession.notifySessionStartedLocked(clientReceiver);
}
- @GuardedBy("mLock")
- private boolean isWhitelistedLocked(@NonNull ComponentName componentName) {
- return mWhitelistHelper.isWhitelisted(componentName);
- }
-
// TODO(b/119613670): log metrics
@GuardedBy("mLock")
public void finishSessionLocked(@NonNull String sessionId) {
@@ -457,40 +450,6 @@
}
@GuardedBy("mLock")
- @Nullable
- ContentCaptureOptions getOptionsForPackageLocked(@NonNull String packageName) {
- if (!mWhitelistHelper.isWhitelisted(packageName)) {
- if (packageName.equals(getServicePackageName())) {
- if (mMaster.verbose) Slog.v(mTag, "getOptionsForPackage() lite for " + packageName);
- return new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel);
- }
- if (mMaster.verbose) {
- Slog.v(mTag, "getOptionsForPackage(" + packageName + "): not whitelisted");
- }
- return null;
- }
-
- final ArraySet<ComponentName> whitelistedComponents = mWhitelistHelper
- .getWhitelistedComponents(packageName);
- if (Build.IS_USER && isTemporaryServiceSetLocked()) {
- final String servicePackageName = getServicePackageName();
- if (!packageName.equals(servicePackageName)) {
- Slog.w(mTag, "Ignoring package " + packageName
- + " while using temporary service " + servicePackageName);
- return null;
- }
- }
- ContentCaptureOptions options = new ContentCaptureOptions(mMaster.mDevCfgLoggingLevel,
- mMaster.mDevCfgMaxBufferSize, mMaster.mDevCfgIdleFlushingFrequencyMs,
- mMaster.mDevCfgTextChangeFlushingFrequencyMs, mMaster.mDevCfgLogHistorySize,
- whitelistedComponents);
- if (mMaster.verbose) {
- Slog.v(mTag, "getOptionsForPackage(" + packageName + "): " + options);
- }
- return options;
- }
-
- @GuardedBy("mLock")
void onActivityEventLocked(@NonNull ComponentName componentName, @ActivityEventType int type) {
if (mRemoteService == null) {
if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
@@ -522,8 +481,6 @@
mRemoteService.dump(prefix2, pw);
}
- mWhitelistHelper.dump(prefix, "Whitelist", pw);
-
if (mSessions.isEmpty()) {
pw.print(prefix); pw.println("no sessions");
} else {
@@ -560,7 +517,7 @@
if (mMaster.verbose) {
Slog.v(TAG, "resetting content capture whitelist");
}
- mWhitelistHelper.setWhitelist((List) null, null);
+ mMaster.mGlobalContentCaptureOptions.resetWhitelist(mUserId);
}
private final class ContentCaptureServiceRemoteCallback extends
@@ -576,9 +533,7 @@
+ ", " + (activities == null
? "null_activities" : activities.size() + " activities") + ")");
}
- synchronized (mLock) {
- mWhitelistHelper.setWhitelist(packages, activities);
- }
+ mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
}
@Override
diff --git a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
index fa7d3fc..ad04b7d 100644
--- a/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
+++ b/services/core/java/com/android/server/contentcapture/ContentCaptureManagerInternal.java
@@ -49,6 +49,9 @@
/**
* Gets the content capture options for the given user and package, or {@code null} if the
* package is not whitelisted by the service.
+ *
+ * <p><b>NOTE: </b>this method is called by the {@code ActivityManager} service and hence cannot
+ * hold the main service lock.
*/
@Nullable
public abstract ContentCaptureOptions getOptionsForPackage(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index ed894ee..39e93f5 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -181,9 +181,8 @@
mServiceNameResolver = serviceNameResolver;
if (mServiceNameResolver != null) {
- mServiceNameResolver
- .setOnTemporaryServiceNameChangedCallback(
- (u, s) -> updateCachedServiceLocked(u));
+ mServiceNameResolver.setOnTemporaryServiceNameChangedCallback(
+ (u, s, t) -> onServiceNameChanged(u, s, t));
}
if (disallowProperty == null) {
@@ -582,6 +581,23 @@
}
/**
+ * Called when the service name changed (typically when using temporary services).
+ *
+ * <p>By default, it calls {@link #updateCachedServiceLocked(int)}; subclasses must either call
+ * that same method, or {@code super.onServiceNameChanged()}.
+ *
+ * @param userId user handle.
+ * @param serviceName the new service name.
+ * @param isTemporary whether the new service is temporary.
+ */
+ protected void onServiceNameChanged(@UserIdInt int userId, @Nullable String serviceName,
+ boolean isTemporary) {
+ synchronized (mLock) {
+ updateCachedServiceLocked(userId);
+ }
+ }
+
+ /**
* Visits all services in the cache.
*/
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index d204813..35d5956 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -155,7 +155,8 @@
}
mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
- notifyTemporaryServiceNameChangedLocked(userId, componentName);
+ notifyTemporaryServiceNameChangedLocked(userId, componentName,
+ /* isTemporary= */ true);
}
}
@@ -169,7 +170,8 @@
mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
mTemporaryHandler = null;
}
- notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null);
+ notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+ /* isTemporary= */ false);
}
}
@@ -235,9 +237,9 @@
}
private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
- @Nullable String newTemporaryName) {
+ @Nullable String newTemporaryName, boolean isTemporary) {
if (mOnSetCallback != null) {
- mOnSetCallback.onNameResolved(userId, newTemporaryName);
+ mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
}
}
}
diff --git a/services/core/java/com/android/server/infra/ServiceNameResolver.java b/services/core/java/com/android/server/infra/ServiceNameResolver.java
index 8c348ebb..e20c459 100644
--- a/services/core/java/com/android/server/infra/ServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/ServiceNameResolver.java
@@ -39,7 +39,8 @@
/**
* The name change callback.
*/
- void onNameResolved(@UserIdInt int userId, @Nullable String serviceName);
+ void onNameResolved(@UserIdInt int userId, @Nullable String serviceName,
+ boolean isTemporary);
}
/**