Merge changes from topic "sync-2022-02-07"
* changes:
Adapt to new CallerAccess object.
Update framework from Jetpack.
diff --git a/service/java/com/android/server/appsearch/AppSearchManagerService.java b/service/java/com/android/server/appsearch/AppSearchManagerService.java
index c16021c..375e7bd 100644
--- a/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -63,10 +63,12 @@
import com.android.server.SystemService;
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.observer.AppSearchObserverProxy;
import com.android.server.appsearch.stats.StatsCollector;
import com.android.server.appsearch.util.PackageUtil;
+import com.android.server.appsearch.visibilitystore.FrameworkCallerAccess;
import com.android.server.usage.StorageStatsManagerLocal;
import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
@@ -437,10 +439,12 @@
instance.getAppSearchImpl().getSchema(
packageName,
databaseName,
- callingPackageName,
- callingUid,
- instance.getVisibilityChecker()
- .doesCallerHaveSystemAccess(callingPackageName));
+ new FrameworkCallerAccess(
+ callingPackageName,
+ callingUid,
+ instance.getVisibilityChecker()
+ .doesCallerHaveSystemAccess(
+ callingPackageName)));
invokeCallbackOnResult(
callback,
AppSearchResult.newSuccessfulResult(response.getBundle()));
@@ -629,10 +633,12 @@
namespace,
id,
typePropertyPaths,
- callingPackageName,
- callingUid,
- instance.getVisibilityChecker()
- .doesCallerHaveSystemAccess(callingPackageName));
+ new FrameworkCallerAccess(
+ callingPackageName,
+ callingUid,
+ instance.getVisibilityChecker()
+ .doesCallerHaveSystemAccess(
+ callingPackageName)));
} else {
document = instance.getAppSearchImpl().getDocument(
targetPackageName,
@@ -792,9 +798,10 @@
SearchResultPage searchResultPage = instance.getAppSearchImpl().globalQuery(
queryExpression,
new SearchSpec(searchSpecBundle),
- packageName,
- callingUid,
- callerHasSystemAccess,
+ new FrameworkCallerAccess(
+ packageName,
+ callingUid,
+ callerHasSystemAccess),
instance.getLogger());
++operationSuccessCount;
invokeCallbackOnResult(
@@ -1351,9 +1358,11 @@
AppSearchUserInstance instance =
mAppSearchUserInstanceManager.getUserInstance(targetUser);
instance.getAppSearchImpl().addObserver(
- callingPackage,
- callingUid,
- instance.getVisibilityChecker().doesCallerHaveSystemAccess(callingPackage),
+ new FrameworkCallerAccess(
+ callingPackage,
+ callingUid,
+ instance.getVisibilityChecker()
+ .doesCallerHaveSystemAccess(callingPackage)),
targetPackageName,
new ObserverSpec(observerSpecBundle),
EXECUTOR,
diff --git a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
index 9e8c1b1..a23c478 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java
@@ -66,6 +66,7 @@
import com.android.server.appsearch.external.localstorage.stats.SearchStats;
import com.android.server.appsearch.external.localstorage.stats.SetSchemaStats;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityUtil;
@@ -472,126 +473,307 @@
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
-
- SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
-
- SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
- for (int i = 0; i < schemas.size(); i++) {
- AppSearchSchema schema = schemas.get(i);
- SchemaTypeConfigProto schemaTypeProto =
- SchemaToProtoConverter.toSchemaTypeConfigProto(schema, version);
- newSchemaBuilder.addTypes(schemaTypeProto);
+ if (mObserverManager.isPackageObserved(packageName)) {
+ return doSetSchemaWithChangeNotificationLocked(
+ packageName,
+ databaseName,
+ schemas,
+ visibilityDocuments,
+ forceOverride,
+ version,
+ setSchemaStatsBuilder);
+ } else {
+ return doSetSchemaNoChangeNotificationLocked(
+ packageName,
+ databaseName,
+ schemas,
+ visibilityDocuments,
+ forceOverride,
+ version,
+ setSchemaStatsBuilder);
}
-
- String prefix = createPrefix(packageName, databaseName);
- // Combine the existing schema (which may have types from other prefixes) with this
- // prefix's new schema. Modifies the existingSchemaBuilder.
- RewrittenSchemaResults rewrittenSchemaResults =
- rewriteSchema(prefix, existingSchemaBuilder, newSchemaBuilder.build());
-
- // Apply schema
- SchemaProto finalSchema = existingSchemaBuilder.build();
- mLogUtil.piiTrace("setSchema, request", finalSchema.getTypesCount(), finalSchema);
- SetSchemaResultProto setSchemaResultProto =
- mIcingSearchEngineLocked.setSchema(finalSchema, forceOverride);
- mLogUtil.piiTrace(
- "setSchema, response", setSchemaResultProto.getStatus(), setSchemaResultProto);
-
- if (setSchemaStatsBuilder != null) {
- setSchemaStatsBuilder.setStatusCode(
- statusProtoToResultCode(setSchemaResultProto.getStatus()));
- AppSearchLoggerHelper.copyNativeStats(setSchemaResultProto, setSchemaStatsBuilder);
- }
-
- // Determine whether it succeeded.
- try {
- checkSuccess(setSchemaResultProto.getStatus());
- } catch (AppSearchException e) {
- // Swallow the exception for the incompatible change case. We will propagate
- // those deleted schemas and incompatible types to the SetSchemaResponse.
- boolean isFailedPrecondition =
- setSchemaResultProto.getStatus().getCode()
- == StatusProto.Code.FAILED_PRECONDITION;
- boolean isIncompatible =
- setSchemaResultProto.getDeletedSchemaTypesCount() > 0
- || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0;
- if (isFailedPrecondition && isIncompatible) {
- return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
- setSchemaResultProto, prefix);
- } else {
- throw e;
- }
- }
-
- // Update derived data structures.
- for (SchemaTypeConfigProto schemaTypeConfigProto :
- rewrittenSchemaResults.mRewrittenPrefixedTypes.values()) {
- addToMap(mSchemaMapLocked, prefix, schemaTypeConfigProto);
- }
-
- for (String schemaType : rewrittenSchemaResults.mDeletedPrefixedTypes) {
- removeFromMap(mSchemaMapLocked, prefix, schemaType);
- }
- // Since the constructor of VisibilityStore will set schema. Avoid call visibility
- // store before we have already created it.
- if (mVisibilityStoreLocked != null) {
- // Add prefix to all visibility documents.
- List<VisibilityDocument> prefixedVisibilityDocuments =
- new ArrayList<>(visibilityDocuments.size());
- // Find out which Visibility document is deleted or changed to all-default settings.
- // We need to remove them from Visibility Store.
- Set<String> deprecatedVisibilityDocuments =
- new ArraySet<>(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet());
- for (int i = 0; i < visibilityDocuments.size(); i++) {
- VisibilityDocument unPrefixedDocument = visibilityDocuments.get(i);
- // The VisibilityDocument is controlled by the client and it's untrusted but we
- // make it safe by appending a prefix.
- // We must control the package-database prefix. Therefore even if the client
- // fake the id, they can only mess their own app. That's totally allowed and
- // they can do this via the public API too.
- String prefixedSchemaType = prefix + unPrefixedDocument.getId();
- prefixedVisibilityDocuments.add(
- new VisibilityDocument(
- unPrefixedDocument.toBuilder()
- .setId(prefixedSchemaType)
- .build()));
- // This schema has visibility settings. We should keep it from the removal list.
- deprecatedVisibilityDocuments.remove(prefixedSchemaType);
- }
- // Now deprecatedVisibilityDocuments contains those existing schemas that has
- // all-default visibility settings, add deleted schemas. That's all we need to
- // remove.
- deprecatedVisibilityDocuments.addAll(rewrittenSchemaResults.mDeletedPrefixedTypes);
- mVisibilityStoreLocked.removeVisibility(deprecatedVisibilityDocuments);
- mVisibilityStoreLocked.setVisibility(prefixedVisibilityDocuments);
- }
- return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
- setSchemaResultProto, prefix);
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
+ * Updates the AppSearch schema for this app, dispatching change notifications.
+ *
+ * @see #setSchema
+ * @see #doSetSchemaNoChangeNotificationLocked
+ */
+ @GuardedBy("mReadWriteLock")
+ @NonNull
+ private SetSchemaResponse doSetSchemaWithChangeNotificationLocked(
+ @NonNull String packageName,
+ @NonNull String databaseName,
+ @NonNull List<AppSearchSchema> schemas,
+ @NonNull List<VisibilityDocument> visibilityDocuments,
+ boolean forceOverride,
+ int version,
+ @Nullable SetSchemaStats.Builder setSchemaStatsBuilder)
+ throws AppSearchException {
+ // First, capture the old state of the system. This includes the old schema as well as
+ // whether each registered observer can access each type. Once VisibilityStore is updated
+ // by the setSchema call, the information of which observers could see which types will be
+ // lost.
+ GetSchemaResponse oldSchema =
+ getSchema(
+ packageName,
+ databaseName,
+ // A CallerAccess object for internal use that has local access to this
+ // database.
+ new CallerAccess(/*callingPackageName=*/ packageName));
+
+ // Cache some lookup tables to help us work with the old schema
+ Set<AppSearchSchema> oldSchemaTypes = oldSchema.getSchemas();
+ Map<String, AppSearchSchema> oldSchemaNameToType = new ArrayMap<>(oldSchemaTypes.size());
+ // Maps unprefixed schema name to the set of listening packages that had visibility into
+ // that type under the old schema.
+ Map<String, Set<String>> oldSchemaNameToVisibleListeningPackage =
+ new ArrayMap<>(oldSchemaTypes.size());
+ for (AppSearchSchema oldSchemaType : oldSchemaTypes) {
+ String oldSchemaName = oldSchemaType.getSchemaType();
+ oldSchemaNameToType.put(oldSchemaName, oldSchemaType);
+ oldSchemaNameToVisibleListeningPackage.put(
+ oldSchemaName,
+ mObserverManager.getObserversForSchemaType(
+ packageName,
+ databaseName,
+ oldSchemaName,
+ mVisibilityStoreLocked,
+ mVisibilityCheckerLocked));
+ }
+
+ // Apply the new schema
+ SetSchemaResponse setSchemaResponse =
+ doSetSchemaNoChangeNotificationLocked(
+ packageName,
+ databaseName,
+ schemas,
+ visibilityDocuments,
+ forceOverride,
+ version,
+ setSchemaStatsBuilder);
+
+ // Cache some lookup tables to help us work with the new schema
+ Map<String, AppSearchSchema> newSchemaNameToType = new ArrayMap<>(schemas.size());
+ // Maps unprefixed schema name to the set of listening packages that have visibility into
+ // that type under the new schema.
+ Map<String, Set<String>> newSchemaNameToVisibleListeningPackage =
+ new ArrayMap<>(schemas.size());
+ for (AppSearchSchema newSchemaType : schemas) {
+ String newSchemaName = newSchemaType.getSchemaType();
+ newSchemaNameToType.put(newSchemaName, newSchemaType);
+ newSchemaNameToVisibleListeningPackage.put(
+ newSchemaName,
+ mObserverManager.getObserversForSchemaType(
+ packageName,
+ databaseName,
+ newSchemaName,
+ mVisibilityStoreLocked,
+ mVisibilityCheckerLocked));
+ }
+
+ // Create a unified set of all schema names mentioned in either the old or new schema.
+ Set<String> allSchemaNames = new ArraySet<>(oldSchemaNameToType.keySet());
+ allSchemaNames.addAll(newSchemaNameToType.keySet());
+
+ // Perform the diff between the old and new schema.
+ for (String schemaName : allSchemaNames) {
+ final AppSearchSchema contentBefore = oldSchemaNameToType.get(schemaName);
+ final AppSearchSchema contentAfter = newSchemaNameToType.get(schemaName);
+
+ final boolean existBefore = (contentBefore != null);
+ final boolean existAfter = (contentAfter != null);
+
+ // This should never happen
+ if (!existBefore && !existAfter) {
+ continue;
+ }
+
+ boolean contentsChanged = true;
+ if (existBefore && existAfter && contentBefore.equals(contentAfter)) {
+ contentsChanged = false;
+ }
+
+ Set<String> oldVisibleListeners =
+ oldSchemaNameToVisibleListeningPackage.get(schemaName);
+ Set<String> newVisibleListeners =
+ newSchemaNameToVisibleListeningPackage.get(schemaName);
+ Set<String> allListeningPackages = new ArraySet<>(oldVisibleListeners);
+ if (newVisibleListeners != null) {
+ allListeningPackages.addAll(newVisibleListeners);
+ }
+
+ // Now that we've computed the relationship between the old and new schema, we go
+ // observer by observer and consider the observer's own personal view of the schema.
+ for (String listeningPackageName : allListeningPackages) {
+ // Figure out the visibility
+ final boolean visibleBefore =
+ (existBefore
+ && oldVisibleListeners != null
+ && oldVisibleListeners.contains(listeningPackageName));
+ final boolean visibleAfter =
+ (existAfter
+ && newVisibleListeners != null
+ && newVisibleListeners.contains(listeningPackageName));
+
+ // Now go through the truth table of all the relevant flags.
+ // visibleBefore and visibleAfter take into account existBefore and existAfter, so
+ // we can stop worrying about existBefore and existAfter.
+ boolean sendNotification = false;
+ if (visibleBefore && visibleAfter && contentsChanged) {
+ sendNotification = true; // Type configuration was modified
+ } else if (!visibleBefore && visibleAfter) {
+ sendNotification = true; // Newly granted visibility or type was created
+ } else if (visibleBefore && !visibleAfter) {
+ sendNotification = true; // Revoked visibility or type was deleted
+ } else {
+ // No visibility before and no visibility after. Nothing to dispatch.
+ }
+
+ if (sendNotification) {
+ mObserverManager.onSchemaChange(
+ /*listeningPackageName=*/ listeningPackageName,
+ /*targetPackageName=*/ packageName,
+ /*databaseName=*/ databaseName,
+ /*schemaName=*/ schemaName);
+ }
+ }
+ }
+
+ return setSchemaResponse;
+ }
+
+ /**
+ * Updates the AppSearch schema for this app, without dispatching change notifications.
+ *
+ * <p>This method can be used only when no one is observing {@code packageName}.
+ *
+ * @see #setSchema
+ * @see #doSetSchemaWithChangeNotificationLocked
+ */
+ @GuardedBy("mReadWriteLock")
+ @NonNull
+ private SetSchemaResponse doSetSchemaNoChangeNotificationLocked(
+ @NonNull String packageName,
+ @NonNull String databaseName,
+ @NonNull List<AppSearchSchema> schemas,
+ @NonNull List<VisibilityDocument> visibilityDocuments,
+ boolean forceOverride,
+ int version,
+ @Nullable SetSchemaStats.Builder setSchemaStatsBuilder)
+ throws AppSearchException {
+ SchemaProto.Builder existingSchemaBuilder = getSchemaProtoLocked().toBuilder();
+
+ SchemaProto.Builder newSchemaBuilder = SchemaProto.newBuilder();
+ for (int i = 0; i < schemas.size(); i++) {
+ AppSearchSchema schema = schemas.get(i);
+ SchemaTypeConfigProto schemaTypeProto =
+ SchemaToProtoConverter.toSchemaTypeConfigProto(schema, version);
+ newSchemaBuilder.addTypes(schemaTypeProto);
+ }
+
+ String prefix = createPrefix(packageName, databaseName);
+ // Combine the existing schema (which may have types from other prefixes) with this
+ // prefix's new schema. Modifies the existingSchemaBuilder.
+ RewrittenSchemaResults rewrittenSchemaResults =
+ rewriteSchema(prefix, existingSchemaBuilder, newSchemaBuilder.build());
+
+ // Apply schema
+ SchemaProto finalSchema = existingSchemaBuilder.build();
+ mLogUtil.piiTrace("setSchema, request", finalSchema.getTypesCount(), finalSchema);
+ SetSchemaResultProto setSchemaResultProto =
+ mIcingSearchEngineLocked.setSchema(finalSchema, forceOverride);
+ mLogUtil.piiTrace(
+ "setSchema, response", setSchemaResultProto.getStatus(), setSchemaResultProto);
+
+ if (setSchemaStatsBuilder != null) {
+ setSchemaStatsBuilder.setStatusCode(
+ statusProtoToResultCode(setSchemaResultProto.getStatus()));
+ AppSearchLoggerHelper.copyNativeStats(setSchemaResultProto, setSchemaStatsBuilder);
+ }
+
+ // Determine whether it succeeded.
+ try {
+ checkSuccess(setSchemaResultProto.getStatus());
+ } catch (AppSearchException e) {
+ // Swallow the exception for the incompatible change case. We will propagate
+ // those deleted schemas and incompatible types to the SetSchemaResponse.
+ boolean isFailedPrecondition =
+ setSchemaResultProto.getStatus().getCode()
+ == StatusProto.Code.FAILED_PRECONDITION;
+ boolean isIncompatible =
+ setSchemaResultProto.getDeletedSchemaTypesCount() > 0
+ || setSchemaResultProto.getIncompatibleSchemaTypesCount() > 0;
+ if (isFailedPrecondition && isIncompatible) {
+ return SetSchemaResponseToProtoConverter.toSetSchemaResponse(
+ setSchemaResultProto, prefix);
+ } else {
+ throw e;
+ }
+ }
+
+ // Update derived data structures.
+ for (SchemaTypeConfigProto schemaTypeConfigProto :
+ rewrittenSchemaResults.mRewrittenPrefixedTypes.values()) {
+ addToMap(mSchemaMapLocked, prefix, schemaTypeConfigProto);
+ }
+
+ for (String schemaType : rewrittenSchemaResults.mDeletedPrefixedTypes) {
+ removeFromMap(mSchemaMapLocked, prefix, schemaType);
+ }
+ // Since the constructor of VisibilityStore will set schema. Avoid call visibility
+ // store before we have already created it.
+ if (mVisibilityStoreLocked != null) {
+ // Add prefix to all visibility documents.
+ List<VisibilityDocument> prefixedVisibilityDocuments =
+ new ArrayList<>(visibilityDocuments.size());
+ // Find out which Visibility document is deleted or changed to all-default settings.
+ // We need to remove them from Visibility Store.
+ Set<String> deprecatedVisibilityDocuments =
+ new ArraySet<>(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet());
+ for (int i = 0; i < visibilityDocuments.size(); i++) {
+ VisibilityDocument unPrefixedDocument = visibilityDocuments.get(i);
+ // The VisibilityDocument is controlled by the client and it's untrusted but we
+ // make it safe by appending a prefix.
+ // We must control the package-database prefix. Therefore even if the client
+ // fake the id, they can only mess their own app. That's totally allowed and
+ // they can do this via the public API too.
+ String prefixedSchemaType = prefix + unPrefixedDocument.getId();
+ prefixedVisibilityDocuments.add(
+ new VisibilityDocument(
+ unPrefixedDocument.toBuilder().setId(prefixedSchemaType).build()));
+ // This schema has visibility settings. We should keep it from the removal list.
+ deprecatedVisibilityDocuments.remove(prefixedSchemaType);
+ }
+ // Now deprecatedVisibilityDocuments contains those existing schemas that has
+ // all-default visibility settings, add deleted schemas. That's all we need to
+ // remove.
+ deprecatedVisibilityDocuments.addAll(rewrittenSchemaResults.mDeletedPrefixedTypes);
+ mVisibilityStoreLocked.removeVisibility(deprecatedVisibilityDocuments);
+ mVisibilityStoreLocked.setVisibility(prefixedVisibilityDocuments);
+ }
+ return SetSchemaResponseToProtoConverter.toSetSchemaResponse(setSchemaResultProto, prefix);
+ }
+
+ /**
* Retrieves the AppSearch schema for this package name, database.
*
* <p>This method belongs to query group.
*
- * @param callerPackageName Package name of the calling app
* @param packageName Package that owns the requested {@link AppSearchSchema} instances.
* @param databaseName Database that owns the requested {@link AppSearchSchema} instances.
+ * @param callerAccess Visibility access info of the calling app
* @throws AppSearchException on IcingSearchEngine error.
*/
- // TODO(b/215624105): The combination of (callerPackageName, callerUid, callerHasSystemAccess)
- // occurs together in many places related to visibility. Should these be combined into a struct
- // called something like CallerAccess?
@NonNull
public GetSchemaResponse getSchema(
@NonNull String packageName,
@NonNull String databaseName,
- @NonNull String callerPackageName,
- int callerUid,
- boolean callerHasSystemAccess)
+ @NonNull CallerAccess callerAccess)
throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
@@ -611,9 +793,7 @@
continue;
}
if (!VisibilityUtil.isSchemaSearchableByCaller(
- callerPackageName,
- callerUid,
- callerHasSystemAccess,
+ callerAccess,
packageName,
prefixedSchemaType,
mVisibilityStoreLocked,
@@ -892,22 +1072,18 @@
* @param id The ID of the document to get.
* @param typePropertyPaths A map of schema type to a list of property paths to return in the
* result.
- * @param callerPackageName The package name of the caller application
- * @param callerUid The ID of the caller application
- * @param callerHasSystemAccess A boolean signifying if the caller has system access
+ * @param callerAccess Visibility access info of the calling app
* @return The Document contents
* @throws AppSearchException on IcingSearchEngine error or invalid permissions
*/
- @Nullable
+ @NonNull
public GenericDocument globalGetDocument(
@NonNull String packageName,
@NonNull String databaseName,
@NonNull String namespace,
@NonNull String id,
@NonNull Map<String, List<String>> typePropertyPaths,
- @NonNull String callerPackageName,
- int callerUid,
- boolean callerHasSystemAccess)
+ @NonNull CallerAccess callerAccess)
throws AppSearchException {
mReadWriteLock.readLock().lock();
try {
@@ -921,9 +1097,7 @@
packageName, databaseName, namespace, id, typePropertyPaths);
if (!VisibilityUtil.isSchemaSearchableByCaller(
- callerPackageName,
- callerUid,
- callerHasSystemAccess,
+ callerAccess,
packageName,
documentProto.getSchema(),
mVisibilityStoreLocked,
@@ -969,7 +1143,6 @@
@NonNull String id,
@NonNull Map<String, List<String>> typePropertyPaths)
throws AppSearchException {
-
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
@@ -1123,11 +1296,7 @@
*
* @param queryExpression Query String to search.
* @param searchSpec Spec for setting filters, raw query etc.
- * @param callerPackageName Package name of the caller, should belong to the {@code
- * callerUserHandle}.
- * @param callerUid UID of the client making the globalQuery call.
- * @param callerHasSystemAccess Whether the caller has been positively identified as having
- * access to schemas marked system surfaceable.
+ * @param callerAccess Visibility access info of the calling app
* @param logger logger to collect globalQuery stats
* @return The results of performing this search. It may contain an empty list of results if no
* documents matched the query.
@@ -1137,16 +1306,16 @@
public SearchResultPage globalQuery(
@NonNull String queryExpression,
@NonNull SearchSpec searchSpec,
- @NonNull String callerPackageName,
- int callerUid,
- boolean callerHasSystemAccess,
+ @NonNull CallerAccess callerAccess,
@Nullable AppSearchLogger logger)
throws AppSearchException {
long totalLatencyStartMillis = SystemClock.elapsedRealtime();
SearchStats.Builder sStatsBuilder = null;
if (logger != null) {
sStatsBuilder =
- new SearchStats.Builder(SearchStats.VISIBILITY_SCOPE_GLOBAL, callerPackageName);
+ new SearchStats.Builder(
+ SearchStats.VISIBILITY_SCOPE_GLOBAL,
+ callerAccess.getCallingPackageName());
}
mReadWriteLock.readLock().lock();
@@ -1175,11 +1344,7 @@
searchSpec, prefixFilters, mNamespaceMapLocked, mSchemaMapLocked);
// Remove those inaccessible schemas.
searchSpecToProtoConverter.removeInaccessibleSchemaFilter(
- callerPackageName,
- callerUid,
- callerHasSystemAccess,
- mVisibilityStoreLocked,
- mVisibilityCheckerLocked);
+ callerAccess, mVisibilityStoreLocked, mVisibilityCheckerLocked);
if (searchSpecToProtoConverter.isNothingToSearch()) {
// there is nothing to search over given their search filters, so we can return an
// empty SearchResult and skip sending request to Icing.
@@ -1187,7 +1352,8 @@
}
SearchResultPage searchResultPage =
doQueryLocked(queryExpression, searchSpecToProtoConverter, sStatsBuilder);
- addNextPageToken(callerPackageName, searchResultPage.getNextPageToken());
+ addNextPageToken(
+ callerAccess.getCallingPackageName(), searchResultPage.getNextPageToken());
return searchResultPage;
} finally {
mReadWriteLock.readLock().unlock();
@@ -2225,10 +2391,8 @@
* will not queue behind I/O. Therefore it is safe to call from any thread including UI or
* binder threads.
*
- * @param listeningPackageName The package name of the app that wants to receive notifications.
- * @param listeningUid The uid of the app that wants to receive notifications.
- * @param listeningPackageHasSystemAccess Whether the app that wants to receive notifications
- * has access to schema types marked 'visible to system'.
+ * @param listeningPackageAccess Visibility information about the app that wants to receive
+ * notifications.
* @param targetPackageName The package that owns the data the observer wants to be notified
* for.
* @param spec Describes the kind of data changes the observer should trigger for.
@@ -2237,9 +2401,7 @@
* @param observer The callback to trigger on notifications.
*/
public void addObserver(
- @NonNull String listeningPackageName,
- int listeningUid,
- boolean listeningPackageHasSystemAccess,
+ @NonNull CallerAccess listeningPackageAccess,
@NonNull String targetPackageName,
@NonNull ObserverSpec spec,
@NonNull Executor executor,
@@ -2249,13 +2411,7 @@
// being created or removed. If we only registered observer for existing types, it would
// be impossible to ever dispatch a notification of a type being added.
mObserverManager.addObserver(
- listeningPackageName,
- listeningUid,
- listeningPackageHasSystemAccess,
- targetPackageName,
- spec,
- executor,
- observer);
+ listeningPackageAccess, targetPackageName, spec, executor, observer);
}
/**
diff --git a/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java b/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
index ecc7f90..bbd2680 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/ObserverManager.java
@@ -21,17 +21,20 @@
import android.app.appsearch.observer.AppSearchObserverCallback;
import android.app.appsearch.observer.DocumentChangeInfo;
import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.observer.SchemaChangeInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityUtil;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -86,26 +89,22 @@
private static final class ObserverInfo {
/** The package which registered the observer. */
- final String mListeningPackageName;
+ final CallerAccess mListeningPackageAccess;
- final int mListeningUid;
- final boolean mListeningPackageHasSystemAccess;
final ObserverSpec mObserverSpec;
final Executor mExecutor;
final AppSearchObserverCallback mObserver;
// Values is a set of document IDs
volatile Map<DocumentChangeGroupKey, Set<String>> mDocumentChanges = new ArrayMap<>();
+ // Keys are database prefixes, values are a set of schema names
+ volatile Map<String, Set<String>> mSchemaChanges = new ArrayMap<>();
ObserverInfo(
- @NonNull String listeningPackageName,
- int listeningUid,
- boolean listeningPackageHasSystemAccess,
+ @NonNull CallerAccess listeningPackageAccess,
@NonNull ObserverSpec observerSpec,
@NonNull Executor executor,
@NonNull AppSearchObserverCallback observer) {
- mListeningPackageName = Objects.requireNonNull(listeningPackageName);
- mListeningUid = listeningUid;
- mListeningPackageHasSystemAccess = listeningPackageHasSystemAccess;
+ mListeningPackageAccess = Objects.requireNonNull(listeningPackageAccess);
mObserverSpec = Objects.requireNonNull(observerSpec);
mExecutor = Objects.requireNonNull(executor);
mObserver = Objects.requireNonNull(observer);
@@ -114,7 +113,7 @@
private final Object mLock = new Object();
- /** Maps observed package to observer infos watching something in that package. */
+ /** Maps target packages to ObserverInfos watching something in that package. */
@GuardedBy("mLock")
private final Map<String, List<ObserverInfo>> mObserversLocked = new ArrayMap<>();
@@ -137,10 +136,8 @@
* will not queue behind I/O. Therefore it is safe to call from any thread including UI or
* binder threads.
*
- * @param listeningPackageName The package name of the app that wants to receive notifications.
- * @param listeningUid The uid of the app that wants to receive notifications.
- * @param listeningPackageHasSystemAccess Whether the app that wants to receive notifications
- * has access to schema types marked 'visible to system'.
+ * @param listeningPackageAccess Visibility information about the app that wants to receive
+ * notifications.
* @param targetPackageName The package that owns the data the observer wants to be notified
* for.
* @param spec Describes the kind of data changes the observer should trigger for.
@@ -149,9 +146,7 @@
* @param observer The callback to trigger on notifications.
*/
public void addObserver(
- @NonNull String listeningPackageName,
- int listeningUid,
- boolean listeningPackageHasSystemAccess,
+ @NonNull CallerAccess listeningPackageAccess,
@NonNull String targetPackageName,
@NonNull ObserverSpec spec,
@NonNull Executor executor,
@@ -162,14 +157,7 @@
infos = new ArrayList<>();
mObserversLocked.put(targetPackageName, infos);
}
- infos.add(
- new ObserverInfo(
- listeningPackageName,
- listeningUid,
- listeningPackageHasSystemAccess,
- spec,
- executor,
- observer));
+ infos.add(new ObserverInfo(listeningPackageAccess, spec, executor, observer));
}
}
@@ -220,18 +208,15 @@
return; // No observers for this type
}
// Enqueue changes for later dispatch once the call returns
+ String prefixedSchema = PrefixUtil.createPrefix(packageName, databaseName) + schemaType;
DocumentChangeGroupKey key = null;
for (int i = 0; i < allObserverInfosForPackage.size(); i++) {
ObserverInfo observerInfo = allObserverInfosForPackage.get(i);
if (!matchesSpec(schemaType, observerInfo.mObserverSpec)) {
continue; // Observer doesn't want this notification
}
- String prefixedSchema =
- PrefixUtil.createPrefix(packageName, databaseName) + schemaType;
if (!VisibilityUtil.isSchemaSearchableByCaller(
- /*callerPackageName=*/ observerInfo.mListeningPackageName,
- observerInfo.mListeningUid,
- observerInfo.mListeningPackageHasSystemAccess,
+ /*callerAccess=*/ observerInfo.mListeningPackageAccess,
/*targetPackageName=*/ packageName,
/*prefixedSchema=*/ prefixedSchema,
visibilityStore,
@@ -254,6 +239,60 @@
}
}
+ /**
+ * Enqueues a change to a schema type for a single observer.
+ *
+ * <p>The notification will be queued in memory for later dispatch. You must call {@link
+ * #dispatchAndClearPendingNotifications} to dispatch all such pending notifications.
+ *
+ * <p>Note that unlike {@link #onDocumentChange}, the changes reported here are not dropped for
+ * observers that don't have visibility. This is because the observer might have had visibility
+ * before the schema change, and a final deletion needs to be sent to it. Caller is responsible
+ * for checking visibility of these notifications.
+ *
+ * @param listeningPackageName Name of package that subscribed to notifications and has been
+ * validated by the caller to have the right access to receive this notification.
+ * @param targetPackageName Name of package that owns the changed schema types.
+ * @param databaseName Database in which the changed schema types reside.
+ * @param schemaName Unprefixed name of the changed schema type.
+ */
+ public void onSchemaChange(
+ @NonNull String listeningPackageName,
+ @NonNull String targetPackageName,
+ @NonNull String databaseName,
+ @NonNull String schemaName) {
+ synchronized (mLock) {
+ List<ObserverInfo> allObserverInfosForPackage = mObserversLocked.get(targetPackageName);
+ if (allObserverInfosForPackage == null || allObserverInfosForPackage.isEmpty()) {
+ return; // No observers for this type
+ }
+ // Enqueue changes for later dispatch once the call returns
+ String prefix = null;
+ for (int i = 0; i < allObserverInfosForPackage.size(); i++) {
+ ObserverInfo observerInfo = allObserverInfosForPackage.get(i);
+ if (!observerInfo
+ .mListeningPackageAccess
+ .getCallingPackageName()
+ .equals(listeningPackageName)) {
+ continue; // Not the observer we've been requested to update right now.
+ }
+ if (!matchesSpec(schemaName, observerInfo.mObserverSpec)) {
+ continue; // Observer doesn't want this notification
+ }
+ if (prefix == null) {
+ prefix = PrefixUtil.createPrefix(targetPackageName, databaseName);
+ }
+ Set<String> changedSchemaNames = observerInfo.mSchemaChanges.get(prefix);
+ if (changedSchemaNames == null) {
+ changedSchemaNames = new ArraySet<>();
+ observerInfo.mSchemaChanges.put(prefix, changedSchemaNames);
+ }
+ changedSchemaNames.add(schemaName);
+ }
+ mHasNotifications = true;
+ }
+ }
+
/** Returns whether there are any observers registered to watch the given package. */
public boolean isPackageObserved(@NonNull String packageName) {
synchronized (mLock) {
@@ -281,6 +320,44 @@
}
}
+ /**
+ * Returns package names of listening packages registered for changes on the given {@code
+ * packageName}, {@code databaseName} and unprefixed {@code schemaType}, only if they have
+ * access to that type according to the provided {@code visibilityChecker}.
+ */
+ @NonNull
+ public Set<String> getObserversForSchemaType(
+ @NonNull String packageName,
+ @NonNull String databaseName,
+ @NonNull String schemaType,
+ @Nullable VisibilityStore visibilityStore,
+ @Nullable VisibilityChecker visibilityChecker) {
+ synchronized (mLock) {
+ List<ObserverInfo> allObserverInfosForPackage = mObserversLocked.get(packageName);
+ if (allObserverInfosForPackage == null) {
+ return Collections.emptySet();
+ }
+ Set<String> result = new ArraySet<>();
+ String prefixedSchema = PrefixUtil.createPrefix(packageName, databaseName) + schemaType;
+ for (int i = 0; i < allObserverInfosForPackage.size(); i++) {
+ ObserverInfo observerInfo = allObserverInfosForPackage.get(i);
+ if (!matchesSpec(schemaType, observerInfo.mObserverSpec)) {
+ continue; // Observer doesn't want this notification
+ }
+ if (!VisibilityUtil.isSchemaSearchableByCaller(
+ /*callerAccess=*/ observerInfo.mListeningPackageAccess,
+ /*targetPackageName=*/ packageName,
+ /*prefixedSchema=*/ prefixedSchema,
+ visibilityStore,
+ visibilityChecker)) {
+ continue; // Observer can't have this notification.
+ }
+ result.add(observerInfo.mListeningPackageAccess.getCallingPackageName());
+ }
+ return result;
+ }
+ }
+
/** Returns whether any notifications have been queued for dispatch. */
public boolean hasNotifications() {
return mHasNotifications;
@@ -308,33 +385,63 @@
@GuardedBy("mLock")
private void dispatchAndClearPendingNotificationsLocked(@NonNull ObserverInfo observerInfo) {
// Get and clear the pending changes
+ Map<String, Set<String>> schemaChanges = observerInfo.mSchemaChanges;
Map<DocumentChangeGroupKey, Set<String>> documentChanges = observerInfo.mDocumentChanges;
- if (documentChanges.isEmpty()) {
+ if (schemaChanges.isEmpty() && documentChanges.isEmpty()) {
return;
}
- observerInfo.mDocumentChanges = new ArrayMap<>();
+ if (!schemaChanges.isEmpty()) {
+ observerInfo.mSchemaChanges = new ArrayMap<>();
+ }
+ if (!documentChanges.isEmpty()) {
+ observerInfo.mDocumentChanges = new ArrayMap<>();
+ }
// Dispatch the pending changes
observerInfo.mExecutor.execute(
() -> {
- for (Map.Entry<DocumentChangeGroupKey, Set<String>> entry :
- documentChanges.entrySet()) {
- DocumentChangeInfo documentChangeInfo =
- new DocumentChangeInfo(
- entry.getKey().mPackageName,
- entry.getKey().mDatabaseName,
- entry.getKey().mNamespace,
- entry.getKey().mSchemaName,
- entry.getValue());
+ // Schema changes
+ if (!schemaChanges.isEmpty()) {
+ for (Map.Entry<String, Set<String>> entry : schemaChanges.entrySet()) {
+ SchemaChangeInfo schemaChangeInfo =
+ new SchemaChangeInfo(
+ /*packageName=*/ PrefixUtil.getPackageName(
+ entry.getKey()),
+ /*databaseName=*/ PrefixUtil.getDatabaseName(
+ entry.getKey()),
+ /*changedSchemaNames=*/ entry.getValue());
- try {
- // TODO(b/193494000): Add code to dispatch SchemaChangeInfo too.
- observerInfo.mObserver.onDocumentChanged(documentChangeInfo);
- } catch (Throwable t) {
- Log.w(
- TAG,
- "AppSearchObserverCallback threw exception during dispatch",
- t);
+ try {
+ observerInfo.mObserver.onSchemaChanged(schemaChangeInfo);
+ } catch (Throwable t) {
+ Log.w(
+ TAG,
+ "AppSearchObserverCallback threw exception during dispatch",
+ t);
+ }
+ }
+ }
+
+ // Document changes
+ if (!documentChanges.isEmpty()) {
+ for (Map.Entry<DocumentChangeGroupKey, Set<String>> entry :
+ documentChanges.entrySet()) {
+ DocumentChangeInfo documentChangeInfo =
+ new DocumentChangeInfo(
+ entry.getKey().mPackageName,
+ entry.getKey().mDatabaseName,
+ entry.getKey().mNamespace,
+ entry.getKey().mSchemaName,
+ entry.getValue());
+
+ try {
+ observerInfo.mObserver.onDocumentChanged(documentChangeInfo);
+ } catch (Throwable t) {
+ Log.w(
+ TAG,
+ "AppSearchObserverCallback threw exception during dispatch",
+ t);
+ }
}
}
});
diff --git a/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
index f396a12..7a68fa1 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java
@@ -28,6 +28,7 @@
import android.util.ArraySet;
import android.util.Log;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityUtil;
@@ -155,18 +156,14 @@
* For each target schema, we will check visibility store is that accessible to the caller. And
* remove this schemas if it is not allowed for caller to query.
*
- * @param callerPackageName The package name of caller
- * @param callerUid The uid of the caller.
- * @param callerHasSystemAccess Whether the caller has system access.
+ * @param callerAccess Visibility access info of the calling app
* @param visibilityStore The {@link VisibilityStore} that store all visibility information.
* @param visibilityChecker Optional visibility checker to check whether the caller could access
* target schemas. Pass {@code null} will reject access for all documents which doesn't
* belong to the calling package.
*/
public void removeInaccessibleSchemaFilter(
- @NonNull String callerPackageName,
- int callerUid,
- boolean callerHasSystemAccess,
+ @NonNull CallerAccess callerAccess,
@Nullable VisibilityStore visibilityStore,
@Nullable VisibilityChecker visibilityChecker) {
Iterator<String> targetPrefixedSchemaFilterIterator =
@@ -176,9 +173,7 @@
String packageName = getPackageName(targetPrefixedSchemaFilter);
if (!VisibilityUtil.isSchemaSearchableByCaller(
- callerPackageName,
- callerUid,
- callerHasSystemAccess,
+ callerAccess,
packageName,
targetPrefixedSchemaFilter,
visibilityStore,
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/CallerAccess.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/CallerAccess.java
new file mode 100644
index 0000000..743d732
--- /dev/null
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/CallerAccess.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 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.appsearch.external.localstorage.visibilitystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes of an API caller relevant to its access via visibility store.
+ *
+ * @hide
+ */
+public class CallerAccess {
+ private final String mCallingPackageName;
+
+ /**
+ * Constructs a new {@link CallerAccess}.
+ *
+ * @param callingPackageName The name of the package which wants to access data.
+ */
+ public CallerAccess(@NonNull String callingPackageName) {
+ mCallingPackageName = Objects.requireNonNull(callingPackageName);
+ }
+
+ /** Returns the name of the package which wants to access data. */
+ @NonNull
+ public String getCallingPackageName() {
+ return mCallingPackageName;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CallerAccess)) return false;
+ CallerAccess that = (CallerAccess) o;
+ return mCallingPackageName.equals(that.mCallingPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return mCallingPackageName.hashCode();
+ }
+}
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityChecker.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityChecker.java
index bbca403..1fc37f2 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityChecker.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityChecker.java
@@ -26,16 +26,14 @@
/**
* Checks whether the given caller has access to the given prefixed schemas.
*
+ * @param callerAccess Visibility access info of the calling app
* @param packageName Package of app that owns the schemas.
* @param prefixedSchema The prefixed schema type that the caller want to access.
- * @param callerUid UID of the app that wants to see the data.
- * @param callerHasSystemAccess whether the caller has system access.
* @param visibilityStore The {@link VisibilityStore} that store all visibility information.
*/
boolean isSchemaSearchableByCaller(
+ @NonNull CallerAccess callerAccess,
@NonNull String packageName,
@NonNull String prefixedSchema,
- int callerUid,
- boolean callerHasSystemAccess,
@NonNull VisibilityStore visibilityStore);
}
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
index 22466c8..f7f6ba8 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityStore.java
@@ -25,7 +25,6 @@
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.VisibilityDocument;
import android.app.appsearch.exceptions.AppSearchException;
-import android.os.Process;
import android.util.ArrayMap;
import android.util.Log;
@@ -79,9 +78,7 @@
mAppSearchImpl.getSchema(
VISIBILITY_PACKAGE_NAME,
VISIBILITY_DATABASE_NAME,
- /*callerPackageName=*/ VISIBILITY_PACKAGE_NAME,
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false);
+ new CallerAccess(/*callingPackageName=*/ VISIBILITY_PACKAGE_NAME));
switch (getSchemaResponse.getVersion()) {
case VisibilityDocument.SCHEMA_VERSION_DOC_PER_PACKAGE:
maybeMigrateToLatest(getSchemaResponse);
diff --git a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityUtil.java b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityUtil.java
index 8cc4dbd..823947a 100644
--- a/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityUtil.java
+++ b/service/java/com/android/server/appsearch/external/localstorage/visibilitystore/VisibilityUtil.java
@@ -35,10 +35,7 @@
* <p>Correctly handles access to own data and the situation that visibilityStore and
* visibilityChecker are not configured.
*
- * @param callerPackageName The package name of the app that wants to access the data.
- * @param callerUid The uid of app that wants to access the data.
- * @param callerHasSystemAccess Whether the app that wants to access the data has access to
- * schema types marked visible to the system.
+ * @param callerAccess Visibility access info of the calling app
* @param targetPackageName The package name of the app that owns the data.
* @param prefixedSchema The prefixed schema type the caller wants to access.
* @param visibilityStore Store for visibility information. If not provided, only access to own
@@ -48,27 +45,22 @@
* @return Whether access by the caller to this prefixed schema should be allowed.
*/
public static boolean isSchemaSearchableByCaller(
- @NonNull String callerPackageName,
- int callerUid,
- boolean callerHasSystemAccess,
+ @NonNull CallerAccess callerAccess,
@NonNull String targetPackageName,
@NonNull String prefixedSchema,
@Nullable VisibilityStore visibilityStore,
@Nullable VisibilityChecker visibilityChecker) {
- Objects.requireNonNull(callerPackageName);
+ Objects.requireNonNull(callerAccess);
Objects.requireNonNull(targetPackageName);
Objects.requireNonNull(prefixedSchema);
- if (callerPackageName.equals(targetPackageName)) {
+
+ if (callerAccess.getCallingPackageName().equals(targetPackageName)) {
return true; // Everyone is always allowed to retrieve their own data.
}
if (visibilityStore == null || visibilityChecker == null) {
return false; // No visibility is configured at this time; no other access possible.
}
return visibilityChecker.isSchemaSearchableByCaller(
- targetPackageName,
- prefixedSchema,
- callerUid,
- callerHasSystemAccess,
- visibilityStore);
+ callerAccess, targetPackageName, prefixedSchema, visibilityStore);
}
}
diff --git a/service/java/com/android/server/appsearch/visibilitystore/FrameworkCallerAccess.java b/service/java/com/android/server/appsearch/visibilitystore/FrameworkCallerAccess.java
new file mode 100644
index 0000000..048c1b9
--- /dev/null
+++ b/service/java/com/android/server/appsearch/visibilitystore/FrameworkCallerAccess.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 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.appsearch.visibilitystore;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes of an API caller relevant to its access via visibility store.
+ *
+ * @hide
+ */
+public class FrameworkCallerAccess extends CallerAccess {
+ private final int mCallingUid;
+ private final boolean mCallerHasSystemAccess;
+
+ /**
+ * Constructs a new {@link CallerAccess}.
+ *
+ * @param callingPackageName The name of the package which wants to access data.
+ * @param callingUid The uid of the package which wants to access data.
+ * @param callerHasSystemAccess Whether {@code callingPackageName} has access to schema types
+ * marked visible to system via {@link
+ * android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}.
+ */
+ public FrameworkCallerAccess(
+ @NonNull String callingPackageName, int callingUid, boolean callerHasSystemAccess) {
+ super(callingPackageName);
+ mCallingUid = callingUid;
+ mCallerHasSystemAccess = callerHasSystemAccess;
+ }
+
+ /** Returns the uid of the package which wants to access data. */
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ /**
+ * Returns whether {@code callingPackageName} has access to schema types marked visible to
+ * system via {@link
+ * android.app.appsearch.SetSchemaRequest.Builder#setSchemaTypeDisplayedBySystem}.
+ */
+ public boolean doesCallerHaveSystemAccess() {
+ return mCallerHasSystemAccess;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FrameworkCallerAccess)) return false;
+ FrameworkCallerAccess that = (FrameworkCallerAccess) o;
+ return super.equals(that)
+ && mCallingUid == that.mCallingUid
+ && mCallerHasSystemAccess == that.mCallerHasSystemAccess;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCallingUid, mCallerHasSystemAccess);
+ }
+}
diff --git a/service/java/com/android/server/appsearch/visibilitystore/VisibilityCheckerImpl.java b/service/java/com/android/server/appsearch/visibilitystore/VisibilityCheckerImpl.java
index 4ab0a56..767b86f 100644
--- a/service/java/com/android/server/appsearch/visibilitystore/VisibilityCheckerImpl.java
+++ b/service/java/com/android/server/appsearch/visibilitystore/VisibilityCheckerImpl.java
@@ -23,13 +23,11 @@
import android.content.pm.PackageManager;
import android.os.UserHandle;
-import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.util.PackageUtil;
-import com.google.android.icing.proto.PersistType;
-
import java.util.Objects;
/**
@@ -47,31 +45,33 @@
@Override
public boolean isSchemaSearchableByCaller(
+ @NonNull CallerAccess callerAccess,
@NonNull String packageName,
@NonNull String prefixedSchema,
- int callerUid,
- boolean callerHasSystemAccess,
@NonNull VisibilityStore visibilityStore) {
+ Objects.requireNonNull(callerAccess);
Objects.requireNonNull(packageName);
Objects.requireNonNull(prefixedSchema);
if (packageName.equals(VisibilityStore.VISIBILITY_PACKAGE_NAME)) {
return false; // VisibilityStore schemas are for internal bookkeeping.
}
- VisibilityDocument visibilityDocument = visibilityStore.getVisibility(prefixedSchema);
+ FrameworkCallerAccess frameworkCallerAccess = (FrameworkCallerAccess) callerAccess;
+ VisibilityDocument visibilityDocument = visibilityStore.getVisibility(prefixedSchema);
if (visibilityDocument == null) {
// The target schema doesn't exist yet. We will treat it as default setting and the only
// accessible case is that the caller has system access.
- return callerHasSystemAccess;
+ return frameworkCallerAccess.doesCallerHaveSystemAccess();
}
- if (callerHasSystemAccess && !visibilityDocument.isNotDisplayedBySystem()) {
+ if (frameworkCallerAccess.doesCallerHaveSystemAccess() &&
+ !visibilityDocument.isNotDisplayedBySystem()) {
return true;
}
// May not be platform surfaceable, but might still be accessible through 3p access.
- return isSchemaVisibleToPackages(visibilityDocument, callerUid);
+ return isSchemaVisibleToPackages(visibilityDocument, frameworkCallerAccess.getCallingUid());
}
/**
diff --git a/synced_jetpack_changeid.txt b/synced_jetpack_changeid.txt
index 7dfd853..715c7de 100644
--- a/synced_jetpack_changeid.txt
+++ b/synced_jetpack_changeid.txt
@@ -1 +1 @@
-5df0a880c467b3853603ebd196a4d3682974f1d8
+dd180bba39024c5a21377dfb1201697474195865
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
index a4017e9..b0c69d0 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java
@@ -38,9 +38,9 @@
import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.observer.DocumentChangeInfo;
import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.observer.SchemaChangeInfo;
import android.app.appsearch.testutil.TestObserverCallback;
import android.content.Context;
-import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -50,6 +50,7 @@
import com.android.server.appsearch.external.localstorage.stats.InitializeStats;
import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker;
import com.android.server.appsearch.icing.proto.DocumentProto;
import com.android.server.appsearch.icing.proto.GetOptimizeInfoResultProto;
@@ -91,6 +92,10 @@
private File mAppSearchDir;
private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ // The caller access for this package
+ private final CallerAccess mSelfCallerAccess = new CallerAccess(mContext.getPackageName());
+
private AppSearchImpl mAppSearchImpl;
@Before
@@ -518,9 +523,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
- mContext.getPackageName(),
- Process.INVALID_UID,
- /*callerHasSystemAccess=*/ false,
+ mSelfCallerAccess,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
@@ -579,18 +582,14 @@
.getSchema(
/*packageName=*/ mContext.getPackageName(),
/*databaseName=*/ "database1",
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false)
+ /*callerAccess=*/ mSelfCallerAccess)
.getSchemas())
.isEmpty();
results =
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
- mContext.getPackageName(),
- Process.INVALID_UID,
- /*callerHasSystemAccess=*/ false,
+ mSelfCallerAccess,
/*logger=*/ null);
assertThat(results.getResults()).isEmpty();
@@ -613,9 +612,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
new SearchSpec.Builder().addFilterSchemas("Type1").build(),
- mContext.getPackageName(),
- Process.INVALID_UID,
- /*callerHasSystemAccess=*/ false,
+ mSelfCallerAccess,
/*logger=*/ null);
assertThat(results.getResults()).hasSize(1);
assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc);
@@ -748,16 +745,14 @@
}
@Test
- public void testGlobalQueryEmptyDatabase() throws Exception {
+ public void testGlobalQuery_emptyPackage() throws Exception {
SearchSpec searchSpec =
new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build();
SearchResultPage searchResultPage =
mAppSearchImpl.globalQuery(
- "",
+ /*queryExpression=*/ "",
searchSpec,
- /*callerPackageName=*/ "",
- Process.INVALID_UID,
- /*callerHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ ""),
/*logger=*/ null);
assertThat(searchResultPage.getResults()).isEmpty();
}
@@ -894,9 +889,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
- "package1",
- Process.myUid(),
- /*callerHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ "package1"),
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
@@ -943,9 +936,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
- "package1",
- Process.myUid(),
- /*callerHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ "package1"),
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
@@ -1155,9 +1146,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
- "package1",
- Process.myUid(),
- /*callerHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ "package1"),
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
@@ -1215,9 +1204,7 @@
mAppSearchImpl.globalQuery(
/*queryExpression=*/ "",
searchSpec,
- "package1",
- Process.myUid(),
- /*callerHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ "package1"),
/*logger=*/ null);
// Document2 will come first because it was inserted last and default return order is
@@ -2085,9 +2072,7 @@
mAppSearchImpl.getSchema(
/*packageName=*/ "package",
/*databaseName=*/ "database",
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false));
+ /*callerAccess=*/ mSelfCallerAccess));
assertThrows(
IllegalStateException.class,
@@ -2120,9 +2105,7 @@
mAppSearchImpl.globalQuery(
"query",
new SearchSpec.Builder().build(),
- "package",
- Process.INVALID_UID,
- /*callerHasSystemAccess=*/ false,
+ mSelfCallerAccess,
/*logger=*/ null));
assertThrows(
@@ -3161,17 +3144,13 @@
// Register an observer twice, on different packages.
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- /*listeningPackageName=*/ mContext.getPackageName(),
- /*listeningUid=*/ Process.myUid(),
- /*listeningPackageHasSystemAccess=*/ false,
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
observer);
mAppSearchImpl.addObserver(
- /*listeningPackageName=*/ mContext.getPackageName(),
- /*listeningUid=*/ Process.myUid(),
- /*listeningPackageHasSystemAccess=*/ false,
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
/*targetPackageName=*/ fakePackage,
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -3219,8 +3198,7 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- false;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> false;
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
@@ -3254,9 +3232,7 @@
"namespace1",
"id1",
/*typePropertyPaths=*/ Collections.emptyMap(),
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false));
+ /*callerAccess=*/ mSelfCallerAccess));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(e.getMessage()).isEqualTo("Document (namespace1, id1) not found.");
}
@@ -3270,8 +3246,7 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- true;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
@@ -3302,9 +3277,7 @@
"namespace1",
"id1",
/*typePropertyPaths=*/ Collections.emptyMap(),
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false);
+ /*callerAccess=*/ mSelfCallerAccess);
assertThat(getResult).isEqualTo(document);
}
@@ -3317,8 +3290,7 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- true;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
@@ -3352,9 +3324,7 @@
"namespace1",
"id2",
/*typePropertyPaths=*/ Collections.emptyMap(),
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false));
+ /*callerAccess=*/ mSelfCallerAccess));
assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND);
assertThat(e.getMessage()).isEqualTo("Document (namespace1, id2) not found.");
}
@@ -3367,8 +3337,8 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- callerUid == 1;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) ->
+ callerAccess.getCallingPackageName().equals("visiblePackage");
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
@@ -3402,9 +3372,8 @@
"namespace1",
"id1",
/*typePropertyPaths=*/ Collections.emptyMap(),
- /*callerPackageName=*/ mContext.getPackageName(),
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ false));
+ new CallerAccess(
+ /*callingPackageName=*/ "invisiblePackage")));
mAppSearchImpl.remove(
"package", "database", "namespace1", "id1", /*removeStatsBuilder=*/ null);
@@ -3419,9 +3388,8 @@
"namespace1",
"id1",
/*typePropertyPaths=*/ Collections.emptyMap(),
- /*callerPackageName=*/ "package",
- /*callerUid=*/ Process.myUid(),
- /*callerHasSystemAccess=*/ true));
+ new CallerAccess(
+ /*callingPackageName=*/ "visiblePackage")));
assertThat(noDocException.getResultCode()).isEqualTo(unauthorizedException.getResultCode());
assertThat(noDocException.getMessage()).isEqualTo(unauthorizedException.getMessage());
@@ -3647,8 +3615,7 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- true;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> true;
mAppSearchImpl =
AppSearchImpl.create(
tempFolder,
@@ -3674,9 +3641,8 @@
mAppSearchImpl.getSchema(
"package",
"database",
- /*callerPackageName=*/ "com.android.appsearch.fake.package",
- /*callerUid=*/ 1,
- /*callerHasSystemAccess=*/ false);
+ new CallerAccess(
+ /*callingPackageName=*/ "com.android.appsearch.fake.package"));
assertThat(getResponse.getSchemas()).containsExactlyElementsIn(schemas);
assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).containsExactly("Type");
}
@@ -3698,9 +3664,7 @@
mAppSearchImpl.getSchema(
"com.android.appsearch.fake.package",
"database",
- /*callerPackageName=*/ "package",
- /*callerUid=*/ 1,
- /*callerHasSystemAccess=*/ false);
+ new CallerAccess(/*callingPackageName=*/ "package"));
assertThat(getResponse.getSchemas()).isEmpty();
assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).isEmpty();
}
@@ -3721,9 +3685,8 @@
mAppSearchImpl.getSchema(
"package",
"database",
- /*callerPackageName=*/ "com.android.fake.package",
- /*callerUid=*/ 1,
- /*callerHasSystemAccess=*/ false);
+ new CallerAccess(
+ /*callingPackageName=*/ "com.android.appsearch.fake.package"));
assertThat(getResponse.getSchemas()).isEmpty();
assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).isEmpty();
assertThat(getResponse.getVersion()).isEqualTo(0);
@@ -3732,11 +3695,7 @@
// from the same package
getResponse =
mAppSearchImpl.getSchema(
- "package",
- "database",
- /*callerPackageName=*/ "package",
- /*callerUid=*/ 1,
- /*callerHasSystemAccess=*/ false);
+ "package", "database", new CallerAccess(/*callingPackageName=*/ "package"));
assertThat(getResponse.getSchemas()).containsExactlyElementsIn(schemas);
}
@@ -3751,7 +3710,7 @@
mAppSearchImpl.close();
File tempFolder = mTemporaryFolder.newFolder();
VisibilityChecker mockVisibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
+ (callerAccess, packageName, prefixedSchema, visibilityStore) ->
prefixedSchema.endsWith("VisibleType");
mAppSearchImpl =
AppSearchImpl.create(
@@ -3780,9 +3739,8 @@
mAppSearchImpl.getSchema(
"package",
"database",
- /*callerPackageName=*/ "com.android.appsearch.fake.package",
- /*callerUid=*/ 1,
- /*callerHasSystemAccess=*/ false);
+ new CallerAccess(
+ /*callingPackageName=*/ "com.android.appsearch.fake.package"));
assertThat(getResponse.getSchemas()).containsExactly(schemas.get(0));
assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).containsExactly("VisibleType");
assertThat(getResponse.getVersion()).isEqualTo(1);
@@ -3802,9 +3760,7 @@
// Register an observer
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- /*listeningPackageName=*/ mContext.getPackageName(),
- Process.myUid(),
- /*listeningPackageHasSystemAccess=*/ false,
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -3838,8 +3794,7 @@
public void testDispatchObserver_samePackage_withVisStore_accept() throws Exception {
// Make a visibility checker that rejects everything
final VisibilityChecker rejectChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- false;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> false;
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
@@ -3861,9 +3816,7 @@
// Register an observer
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- /*listeningPackageName=*/ mContext.getPackageName(),
- Process.myUid(),
- /*listeningPackageHasSystemAccess=*/ false,
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -3907,9 +3860,7 @@
// Register an observer from a simulated different package
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- /*listeningPackageName=*/ "com.fake.Listening.package",
- Process.myUid(),
- /*listeningPackageHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ "com.fake.Listening.package"),
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -3935,12 +3886,11 @@
@Test
public void testDispatchObserver_differentPackage_withVisStore_accept() throws Exception {
final String fakeListeningPackage = "com.fake.listening.package";
- final int fakeListeningUid = 42;
// Make a visibility checker that allows only fakeListeningPackage.
final VisibilityChecker visibilityChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- callerUid == fakeListeningUid;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) ->
+ callerAccess.getCallingPackageName().equals(fakeListeningPackage);
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
@@ -3962,9 +3912,7 @@
// Register an observer
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- fakeListeningPackage,
- fakeListeningUid,
- /*listeningPackageHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ fakeListeningPackage),
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -3997,12 +3945,10 @@
@Test
public void testDispatchObserver_differentPackage_withVisStore_reject() throws Exception {
final String fakeListeningPackage = "com.fake.Listening.package";
- final int fakeListeningUid = 42;
// Make a visibility checker that rejects everything.
final VisibilityChecker rejectChecker =
- (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
- false;
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> false;
mAppSearchImpl.close();
mAppSearchImpl =
AppSearchImpl.create(
@@ -4024,9 +3970,7 @@
// Register an observer
TestObserverCallback observer = new TestObserverCallback();
mAppSearchImpl.addObserver(
- fakeListeningPackage,
- fakeListeningUid,
- /*listeningPackageHasSystemAccess=*/ false,
+ new CallerAccess(/*callingPackageName=*/ fakeListeningPackage),
/*targetPackageName=*/ mContext.getPackageName(),
new ObserverSpec.Builder().build(),
MoreExecutors.directExecutor(),
@@ -4048,4 +3992,720 @@
assertThat(observer.getSchemaChanges()).isEmpty();
assertThat(observer.getDocumentChanges()).isEmpty();
}
+
+ @Test
+ public void testAddObserver_schemaChange_added() throws Exception {
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Add a schema type
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(new AppSearchSchema.Builder("Type1").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Dispatch notifications
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type1")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Add two more schema types without touching the existing one
+ observer.clear();
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2").build(),
+ new AppSearchSchema.Builder("Type3").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Dispatch notifications
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableSet.of("Type2", "Type3")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_removed() throws Exception {
+ // Add a schema type
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Remove Type2
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(new AppSearchSchema.Builder("Type1").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_contents() throws Exception {
+ // Add a schema
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Update the schema, but don't make any actual changes
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 1,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Now update the schema again, but this time actually make a change (cardinality of the
+ // property)
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 2,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_contents_skipBySpec() throws Exception {
+ // Add a schema
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register an observer that only listens for Type2
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ /*listeningPackageAccess=*/ mSelfCallerAccess,
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas("Type2").build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Update both types of the schema (changed cardinalities)
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_visibilityOnly() throws Exception {
+ final String fakeListeningPackage = "com.fake.listening.package";
+
+ // Make a fake visibility checker that actually looks at visibility store
+ final VisibilityChecker visibilityChecker =
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> {
+ if (!callerAccess.getCallingPackageName().equals(fakeListeningPackage)) {
+ return false;
+ }
+ Set<String> allowedPackages =
+ new ArraySet<>(
+ visibilityStore
+ .getVisibility(prefixedSchema)
+ .getPackageNames());
+ return allowedPackages.contains(fakeListeningPackage);
+ };
+ mAppSearchImpl.close();
+ mAppSearchImpl =
+ AppSearchImpl.create(
+ mAppSearchDir,
+ new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null,
+ ALWAYS_OPTIMIZE,
+ visibilityChecker);
+
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakeListeningPackage),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Add a schema where both types are visible to the fake package.
+ List<AppSearchSchema> schemas =
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2").build());
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ schemas,
+ /*visibilityDocuments=*/ ImmutableList.of(
+ new VisibilityDocument.Builder("Type1")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build(),
+ new VisibilityDocument.Builder("Type2")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build()),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Notifications of addition should now be dispatched
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableSet.of("Type1", "Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ observer.clear();
+
+ // Update schema, keeping the types identical but denying visibility to type2
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ schemas,
+ /*visibilityDocuments=*/ ImmutableList.of(
+ new VisibilityDocument.Builder("Type1")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build(),
+ new VisibilityDocument.Builder("Type2").build()),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications. This should look like a deletion of Type2.
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ observer.clear();
+
+ // Now update Type2 and make sure no further notification is received.
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ ImmutableList.of(
+ new VisibilityDocument.Builder("Type1")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build(),
+ new VisibilityDocument.Builder("Type2").build()),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Grant visibility to Type2 again and make sure it appears
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ ImmutableList.of(
+ new VisibilityDocument.Builder("Type1")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build(),
+ new VisibilityDocument.Builder("Type2")
+ .addVisibleToPackage(
+ new PackageIdentifier(fakeListeningPackage, new byte[0]))
+ .build()),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications. This should look like a creation of Type2.
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_visibilityAndContents() throws Exception {
+ final String fakeListeningPackage = "com.fake.listening.package";
+
+ // Make a visibility checker that allows fakeListeningPackage access only to Type2.
+ final VisibilityChecker visibilityChecker =
+ (callerAccess, packageName, prefixedSchema, visibilityStore) ->
+ callerAccess.getCallingPackageName().equals(fakeListeningPackage)
+ && prefixedSchema.endsWith("Type2");
+ mAppSearchImpl.close();
+ mAppSearchImpl =
+ AppSearchImpl.create(
+ mAppSearchDir,
+ new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null,
+ ALWAYS_OPTIMIZE,
+ visibilityChecker);
+
+ // Add a schema.
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_REQUIRED)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakeListeningPackage),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Update both types of the schema (changed cardinalities)
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build(),
+ new AppSearchSchema.Builder("Type2")
+ .addProperty(
+ new AppSearchSchema.BooleanPropertyConfig.Builder(
+ "booleanProp")
+ .setCardinality(
+ AppSearchSchema.PropertyConfig
+ .CARDINALITY_OPTIONAL)
+ .build())
+ .build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_partialVisibility_removed() throws Exception {
+ final String fakeListeningPackage = "com.fake.listening.package";
+
+ // Make a visibility checker that allows fakeListeningPackage access only to Type2.
+ final VisibilityChecker visibilityChecker =
+ (callerAccess, packageName, prefixedSchema, visibilityStore) ->
+ callerAccess.getCallingPackageName().equals(fakeListeningPackage)
+ && prefixedSchema.endsWith("Type2");
+ mAppSearchImpl.close();
+ mAppSearchImpl =
+ AppSearchImpl.create(
+ mAppSearchDir,
+ new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null,
+ ALWAYS_OPTIMIZE,
+ visibilityChecker);
+
+ // Add a schema.
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register an observer
+ TestObserverCallback observer = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakeListeningPackage),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observer);
+
+ // Remove Type1
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(new AppSearchSchema.Builder("Type2").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications. Nothing should appear since Type1 is not visible to us.
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+
+ // Now remove Type2. This should cause a notification.
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+ assertThat(observer.getSchemaChanges()).isEmpty();
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+ assertThat(observer.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type2")));
+ assertThat(observer.getDocumentChanges()).isEmpty();
+ }
+
+ @Test
+ public void testAddObserver_schemaChange_multipleObservers() throws Exception {
+ // Create two fake packages. One can access Type1, one can access Type2, they both can
+ // access Type3, and no one can access Type4.
+ final String fakePackage1 = "com.fake.listening.package1";
+
+ final String fakePackage2 = "com.fake.listening.package2";
+
+ final VisibilityChecker visibilityChecker =
+ (callerAccess, packageName, prefixedSchema, visibilityStore) -> {
+ if (prefixedSchema.endsWith("Type1")) {
+ return callerAccess.getCallingPackageName().equals(fakePackage1);
+ } else if (prefixedSchema.endsWith("Type2")) {
+ return callerAccess.getCallingPackageName().equals(fakePackage2);
+ } else if (prefixedSchema.endsWith("Type3")) {
+ return false;
+ } else if (prefixedSchema.endsWith("Type4")) {
+ return true;
+ } else {
+ throw new IllegalArgumentException(prefixedSchema);
+ }
+ };
+ mAppSearchImpl.close();
+ mAppSearchImpl =
+ AppSearchImpl.create(
+ mAppSearchDir,
+ new UnlimitedLimitConfig(),
+ /*initStatsBuilder=*/ null,
+ ALWAYS_OPTIMIZE,
+ visibilityChecker);
+
+ // Add a schema.
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(
+ new AppSearchSchema.Builder("Type1").build(),
+ new AppSearchSchema.Builder("Type2").build(),
+ new AppSearchSchema.Builder("Type3").build(),
+ new AppSearchSchema.Builder("Type4").build()),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Register three observers: one in each package, and another in package1 with a filter.
+ TestObserverCallback observerPkg1NoFilter = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakePackage1),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observerPkg1NoFilter);
+
+ TestObserverCallback observerPkg2NoFilter = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakePackage2),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().build(),
+ MoreExecutors.directExecutor(),
+ observerPkg2NoFilter);
+
+ TestObserverCallback observerPkg1FilterType4 = new TestObserverCallback();
+ mAppSearchImpl.addObserver(
+ new CallerAccess(/*callingPackageName=*/ fakePackage1),
+ /*targetPackageName=*/ mContext.getPackageName(),
+ new ObserverSpec.Builder().addFilterSchemas("Type4").build(),
+ MoreExecutors.directExecutor(),
+ observerPkg1FilterType4);
+
+ // Remove everything
+ mAppSearchImpl.setSchema(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableList.of(),
+ /*visibilityDocuments=*/ Collections.emptyList(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0,
+ /*setSchemaStatsBuilder=*/ null);
+
+ // Dispatch notifications.
+ mAppSearchImpl.dispatchAndClearChangeNotifications();
+
+ // observerPkg1NoFilter should see Type1 and Type4 vanish.
+ // observerPkg2NoFilter should see Type2 and Type4 vanish.
+ // observerPkg2WithFilter should see Type4 vanish.
+ assertThat(observerPkg1NoFilter.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableSet.of("Type1", "Type4")));
+ assertThat(observerPkg1NoFilter.getDocumentChanges()).isEmpty();
+
+ assertThat(observerPkg2NoFilter.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(),
+ "database1",
+ ImmutableSet.of("Type2", "Type4")));
+ assertThat(observerPkg2NoFilter.getDocumentChanges()).isEmpty();
+
+ assertThat(observerPkg1FilterType4.getSchemaChanges())
+ .containsExactly(
+ new SchemaChangeInfo(
+ mContext.getPackageName(), "database1", ImmutableSet.of("Type4")));
+ assertThat(observerPkg1FilterType4.getDocumentChanges()).isEmpty();
+ }
}
diff --git a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverterTest.java b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverterTest.java
index b9ce1ac..19781fb 100644
--- a/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverterTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverterTest.java
@@ -31,6 +31,7 @@
import com.android.server.appsearch.external.localstorage.OptimizeStrategy;
import com.android.server.appsearch.external.localstorage.UnlimitedLimitConfig;
import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess;
import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
import com.android.server.appsearch.icing.proto.ResultSpecProto;
import com.android.server.appsearch.icing.proto.SchemaTypeConfigProto;
@@ -497,10 +498,8 @@
"package$database/schema3", schemaTypeConfigProto)));
converter.removeInaccessibleSchemaFilter(
- /*callerPackageName=*/ "otherPackageName",
- /*callerUid=*/ -1,
- /*callerHasSystemAccess=*/ true,
- /*visibilityStore=*/ visibilityStore,
+ new CallerAccess(/*callingPackageName=*/ "otherPackageName"),
+ visibilityStore,
AppSearchTestUtils.createMockVisibilityChecker(
/*visiblePrefixedSchemas=*/ ImmutableSet.of(
prefix + "schema1", prefix + "schema3")));
@@ -552,9 +551,7 @@
// remove all target schema filter, and the query becomes nothing to search.
nonEmptyConverter.removeInaccessibleSchemaFilter(
- /*callerPackageName=*/ "otherPackageName",
- /*callerUid=*/ -1,
- /*callerHasSystemAccess=*/ true,
+ new CallerAccess(/*callingPackageName=*/ "otherPackageName"),
/*visibilityStore=*/ null,
/*visibilityChecker=*/ null);
assertThat(nonEmptyConverter.isNothingToSearch()).isTrue();
diff --git a/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityCheckerImplTest.java b/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityCheckerImplTest.java
index 627caba..a1495b8 100644
--- a/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityCheckerImplTest.java
+++ b/testing/servicestests/src/com/android/server/appsearch/visibilitystore/VisibilityCheckerImplTest.java
@@ -133,17 +133,19 @@
ImmutableList.of(visibilityDocument1, visibilityDocument2));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
"prefix/Schema1",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isFalse();
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
"prefix/Schema2",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isFalse();
@@ -152,17 +154,19 @@
mVisibilityStore.setVisibility(
ImmutableList.of(visibilityDocument1, visibilityDocument2));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
"prefix/Schema1",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isTrue();
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
"prefix/Schema2",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isFalse();
}
@@ -208,10 +212,11 @@
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(false);
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaFoo",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
@@ -222,10 +227,11 @@
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaFoo",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
@@ -236,10 +242,11 @@
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaFoo",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isTrue();
@@ -249,10 +256,11 @@
packageNameBar, sha256CertBar, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidBar,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaBar",
- uidBar,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isTrue();
@@ -261,17 +269,19 @@
visibilityDocument2 = new VisibilityDocument.Builder(/*id=*/"prefix/SchemaBar").build();
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument1, visibilityDocument2));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaFoo",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidBar,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaBar",
- uidBar,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
}
@@ -301,10 +311,11 @@
// If we can't verify the Foo package that has access, assume it doesn't have access.
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
"prefix/SchemaFoo",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
}
@@ -330,10 +341,11 @@
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
/*packageName=*/ "",
"$/Schema",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore)).isTrue();
when(mockPackageManager.getPackageUid(eq(packageNameFoo), /*flags=*/ anyInt()))
@@ -342,10 +354,11 @@
packageNameFoo, sha256CertFoo, PackageManager.CERT_INPUT_SHA256))
.thenReturn(true);
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
/*packageName=*/ "",
"$/Schema",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore)).isTrue();
}
@@ -362,10 +375,11 @@
/*id=*/prefix + "Schema").build();
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
prefix + "Schema",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isTrue();
}
@@ -385,10 +399,11 @@
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ mUid,
+ /*callerHasSystemAccess=*/ true),
"package",
prefix + "Schema",
- mUid,
- /*callerHasSystemAccess=*/ true,
mVisibilityStore))
.isFalse();
}
@@ -407,12 +422,12 @@
/*id=*/prefix + "Schema").build();
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument));
- assertThat(mVisibilityChecker
- .isSchemaSearchableByCaller(
+ assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ /*callerUid=*/ 42,
+ /*callerHasSystemAccess=*/ false),
"package",
prefix + "Schema",
- /*callerUid=*/ 42,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isFalse();
}
@@ -444,10 +459,11 @@
mVisibilityStore.setVisibility(ImmutableList.of(visibilityDocument));
assertThat(mVisibilityChecker.isSchemaSearchableByCaller(
+ new FrameworkCallerAccess(/*callingPackageName=*/"package",
+ uidFoo,
+ /*callerHasSystemAccess=*/ false),
"package",
prefix + "Schema",
- uidFoo,
- /*callerHasSystemAccess=*/ false,
mVisibilityStore))
.isTrue();
}
diff --git a/testing/testutils/src/android/app/appsearch/testutil/external/testutil/AppSearchTestUtils.java b/testing/testutils/src/android/app/appsearch/testutil/external/testutil/AppSearchTestUtils.java
index 738777d..28bdc59 100644
--- a/testing/testutils/src/android/app/appsearch/testutil/external/testutil/AppSearchTestUtils.java
+++ b/testing/testutils/src/android/app/appsearch/testutil/external/testutil/AppSearchTestUtils.java
@@ -124,7 +124,7 @@
@NonNull
public static VisibilityChecker createMockVisibilityChecker(
@NonNull Set<String> visiblePrefixedSchemas) {
- return (packageName, prefixedSchema, callerUid, callerHasSystemAccess, visibilityStore) ->
+ return (callerAccess, packageName, prefixedSchema, visibilityStore) ->
visiblePrefixedSchemas.contains(prefixedSchema);
}
}