diff options
| author | 2021-03-23 22:06:59 -0700 | |
|---|---|---|
| committer | 2021-03-23 22:06:59 -0700 | |
| commit | ffb44f506fa35d924167c21bf6509c25db83e8d3 (patch) | |
| tree | 65cbb58124f1b03cb51e967c17175f4489b22d18 | |
| parent | 0b296ebdbe208460684957aaeccc463957bc38d4 (diff) | |
Update framework from jetpack.
Changes included:
* 4d36ab: Rename AppSearchSession in test message to avoid export bug.
* 0b2126: Update AppSearch to be in sync google3 update ag/13878956.
* e79e74: Add getRankingSignal method to SearchResult.
* 2c2383: Add a getNamespaces() API.
* 712009: Add getStorageInfo API.
* 81e601: Add resut grouping flags and limits.
* f23105: Support usage reporting by the system in AppSearchImpl.
* 05b7d3: Change schema version from per AppSearchSchema to overall.
* effe02: Support schema migration to another type.
* a0db44: Some minor fix for GetSchemaResponse
* 66a1c3: Support AppSearchResult to convert a failure result to another type.
Bug: 177266929
Bug: 182620003
Bug: 183031844
Bug: 180429302
Bug: 182909475
Bug: 179160886
Bug: 183042276
Bug: 181179404
Test: Presubmit
Change-Id: I091b25a78093d0c8c193903a58122d09788e31ff
32 files changed, 1824 insertions, 279 deletions
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index e08d22ce194c..a3e05dc7cd85 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -8,6 +8,14 @@ package android.app.appsearch { method public boolean isSuccess(); } + public static final class AppSearchBatchResult.Builder<KeyType, ValueType> { + ctor public AppSearchBatchResult.Builder(); + method @NonNull public android.app.appsearch.AppSearchBatchResult<KeyType,ValueType> build(); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setFailure(@NonNull KeyType, int, @Nullable String); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setResult(@NonNull KeyType, @NonNull android.app.appsearch.AppSearchResult<ValueType>); + method @NonNull public android.app.appsearch.AppSearchBatchResult.Builder<KeyType,ValueType> setSuccess(@NonNull KeyType, @Nullable ValueType); + } + public class AppSearchManager { method public void createGlobalSearchSession(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GlobalSearchSession>>); method public void createSearchSession(@NonNull android.app.appsearch.AppSearchManager.SearchContext, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.AppSearchSession>>); @@ -29,6 +37,8 @@ package android.app.appsearch { method public int getResultCode(); method @Nullable public ValueType getResultValue(); method public boolean isSuccess(); + method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newFailedResult(int, @Nullable String); + method @NonNull public static <ValueType> android.app.appsearch.AppSearchResult<ValueType> newSuccessfulResult(@Nullable ValueType); field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2 field public static final int RESULT_INVALID_ARGUMENT = 3; // 0x3 field public static final int RESULT_INVALID_SCHEMA = 7; // 0x7 @@ -36,13 +46,14 @@ package android.app.appsearch { field public static final int RESULT_NOT_FOUND = 6; // 0x6 field public static final int RESULT_OK = 0; // 0x0 field public static final int RESULT_OUT_OF_SPACE = 5; // 0x5 + field public static final int RESULT_SECURITY_ERROR = 8; // 0x8 field public static final int RESULT_UNKNOWN_ERROR = 1; // 0x1 } public final class AppSearchSchema { method @NonNull public java.util.List<android.app.appsearch.AppSearchSchema.PropertyConfig> getProperties(); method @NonNull public String getSchemaType(); - method @IntRange(from=0) public int getVersion(); + method @Deprecated @IntRange(from=0) public int getVersion(); } public static final class AppSearchSchema.BooleanPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -58,7 +69,7 @@ package android.app.appsearch { ctor public AppSearchSchema.Builder(@NonNull String); method @NonNull public android.app.appsearch.AppSearchSchema.Builder addProperty(@NonNull android.app.appsearch.AppSearchSchema.PropertyConfig); method @NonNull public android.app.appsearch.AppSearchSchema build(); - method @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int); + method @Deprecated @NonNull public android.app.appsearch.AppSearchSchema.Builder setVersion(@IntRange(from=0) int); } public static final class AppSearchSchema.BytesPropertyConfig extends android.app.appsearch.AppSearchSchema.PropertyConfig { @@ -130,7 +141,9 @@ package android.app.appsearch { public final class AppSearchSession implements java.io.Closeable { method public void close(); method public void getByUri(@NonNull android.app.appsearch.GetByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,android.app.appsearch.GenericDocument>); - method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<android.app.appsearch.AppSearchSchema>>>); + method public void getNamespaces(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.util.Set<java.lang.String>>>); + method public void getSchema(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.GetSchemaResponse>>); + method public void getStorageInfo(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.StorageInfo>>); method public void put(@NonNull android.app.appsearch.PutDocumentsRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull android.app.appsearch.RemoveByUriRequest, @NonNull java.util.concurrent.Executor, @NonNull android.app.appsearch.BatchResultCallback<java.lang.String,java.lang.Void>); method public void remove(@NonNull String, @NonNull android.app.appsearch.SearchSpec, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); @@ -203,16 +216,29 @@ package android.app.appsearch { method @Deprecated @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String); } + public class GetSchemaResponse extends java.util.HashSet<android.app.appsearch.AppSearchSchema> { + method @NonNull public java.util.Set<android.app.appsearch.AppSearchSchema> getSchemas(); + method @IntRange(from=0) public int getVersion(); + } + + public static final class GetSchemaResponse.Builder { + ctor public GetSchemaResponse.Builder(); + method @NonNull public android.app.appsearch.GetSchemaResponse.Builder addSchema(@NonNull android.app.appsearch.AppSearchSchema); + method @NonNull public android.app.appsearch.GetSchemaResponse build(); + method @NonNull public android.app.appsearch.GetSchemaResponse.Builder setVersion(@IntRange(from=0) int); + } + public class GlobalSearchSession implements java.io.Closeable { method public void close(); + method public void reportSystemUsage(@NonNull android.app.appsearch.ReportSystemUsageRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<java.lang.Void>>); method @NonNull public android.app.appsearch.SearchResults search(@NonNull String, @NonNull android.app.appsearch.SearchSpec); } public abstract class Migrator { ctor public Migrator(); - ctor public Migrator(int); method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onDowngrade(int, int, @NonNull android.app.appsearch.GenericDocument); method @NonNull @WorkerThread public abstract android.app.appsearch.GenericDocument onUpgrade(int, int, @NonNull android.app.appsearch.GenericDocument); + method public abstract boolean shouldMigrate(int, int); } public class PackageIdentifier { @@ -246,6 +272,21 @@ package android.app.appsearch { method @Deprecated @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String); } + public final class ReportSystemUsageRequest { + method @NonNull public String getDatabaseName(); + method @NonNull public String getNamespace(); + method @NonNull public String getPackageName(); + method @NonNull public String getUri(); + method public long getUsageTimeMillis(); + } + + public static final class ReportSystemUsageRequest.Builder { + ctor public ReportSystemUsageRequest.Builder(@NonNull String, @NonNull String, @NonNull String); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest build(); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUri(@NonNull String); + method @NonNull public android.app.appsearch.ReportSystemUsageRequest.Builder setUsageTimeMillis(long); + } + public final class ReportUsageRequest { method @NonNull public String getNamespace(); method @NonNull public String getUri(); @@ -267,6 +308,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.GenericDocument getGenericDocument(); method @NonNull public java.util.List<android.app.appsearch.SearchResult.MatchInfo> getMatches(); method @NonNull public String getPackageName(); + method public double getRankingSignal(); } public static final class SearchResult.Builder { @@ -274,6 +316,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchResult.Builder addMatch(@NonNull android.app.appsearch.SearchResult.MatchInfo); method @NonNull public android.app.appsearch.SearchResult build(); method @NonNull public android.app.appsearch.SearchResult.Builder setGenericDocument(@NonNull android.app.appsearch.GenericDocument); + method @NonNull public android.app.appsearch.SearchResult.Builder setRankingSignal(double); } public static final class SearchResult.MatchInfo { @@ -315,9 +358,13 @@ package android.app.appsearch { method @NonNull public java.util.Map<java.lang.String,java.util.List<java.lang.String>> getProjections(); method public int getRankingStrategy(); method public int getResultCountPerPage(); + method public int getResultGroupingLimit(); + method public int getResultGroupingTypeFlags(); method public int getSnippetCount(); method public int getSnippetCountPerProperty(); method public int getTermMatch(); + field public static final int GROUPING_TYPE_PER_NAMESPACE = 2; // 0x2 + field public static final int GROUPING_TYPE_PER_PACKAGE = 1; // 0x1 field public static final int ORDER_ASCENDING = 1; // 0x1 field public static final int ORDER_DESCENDING = 0; // 0x0 field public static final String PROJECTION_SCHEMA_TYPE_WILDCARD = "*"; @@ -325,6 +372,8 @@ package android.app.appsearch { field public static final int RANKING_STRATEGY_DOCUMENT_SCORE = 1; // 0x1 field public static final int RANKING_STRATEGY_NONE = 0; // 0x0 field public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; // 0x3 + field public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; // 0x6 + field public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; // 0x7 field public static final int RANKING_STRATEGY_USAGE_COUNT = 4; // 0x4 field public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; // 0x5 field public static final int TERM_MATCH_EXACT_ONLY = 1; // 0x1 @@ -345,6 +394,7 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SearchSpec.Builder setOrder(int); method @NonNull public android.app.appsearch.SearchSpec.Builder setRankingStrategy(int); method @NonNull public android.app.appsearch.SearchSpec.Builder setResultCountPerPage(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_NUM_PER_PAGE) int); + method @NonNull public android.app.appsearch.SearchSpec.Builder setResultGrouping(int, int); method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCount(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_COUNT) int); method @NonNull public android.app.appsearch.SearchSpec.Builder setSnippetCountPerProperty(@IntRange(from=0, to=android.app.appsearch.SearchSpec.MAX_SNIPPET_PER_PROPERTY_COUNT) int); method @NonNull public android.app.appsearch.SearchSpec.Builder setTermMatch(int); @@ -356,6 +406,7 @@ package android.app.appsearch { method @NonNull public java.util.Set<java.lang.String> getSchemasNotDisplayedBySystem(); method @Deprecated @NonNull public java.util.Set<java.lang.String> getSchemasNotVisibleToSystemUi(); method @NonNull public java.util.Map<java.lang.String,java.util.Set<android.app.appsearch.PackageIdentifier>> getSchemasVisibleToPackages(); + method @IntRange(from=1) public int getVersion(); method public boolean isForceOverride(); } @@ -366,9 +417,11 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaRequest build(); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setForceOverride(boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrator(@NonNull String, @NonNull android.app.appsearch.Migrator); + method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setMigrators(@NonNull java.util.Map<java.lang.String,android.app.appsearch.Migrator>); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeDisplayedBySystem(@NonNull String, boolean); method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForPackage(@NonNull String, boolean, @NonNull android.app.appsearch.PackageIdentifier); method @Deprecated @NonNull public android.app.appsearch.SetSchemaRequest.Builder setSchemaTypeVisibilityForSystemUi(@NonNull String, boolean); + method @NonNull public android.app.appsearch.SetSchemaRequest.Builder setVersion(@IntRange(from=1) int); } public class SetSchemaResponse { @@ -407,6 +460,20 @@ package android.app.appsearch { method @NonNull public android.app.appsearch.SetSchemaResponse.MigrationFailure.Builder setUri(@NonNull String); } + public class StorageInfo { + method public int getAliveDocumentsCount(); + method public int getAliveNamespacesCount(); + method public long getSizeBytes(); + } + + public static final class StorageInfo.Builder { + ctor public StorageInfo.Builder(); + method @NonNull public android.app.appsearch.StorageInfo build(); + method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveDocumentsCount(int); + method @NonNull public android.app.appsearch.StorageInfo.Builder setAliveNamespacesCount(int); + method @NonNull public android.app.appsearch.StorageInfo.Builder setSizeBytes(long); + } + } package android.app.appsearch.exceptions { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java index 31ab259127c3..35cea3e2a67b 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -30,10 +30,10 @@ import java.util.Map; /** * Provides results for AppSearch batch operations which encompass multiple documents. * - * <p>Individual results of a batch operation are separated into two maps: one for successes and one - * for failures. For successes, {@link #getSuccesses()} will return a map of keys to instances of - * the value type. For failures, {@link #getFailures()} will return a map of keys to {@link - * AppSearchResult} objects. + * <p>Individual results of a batch operation are separated into two maps: one for successes and + * one for failures. For successes, {@link #getSuccesses()} will return a map of keys to + * instances of the value type. For failures, {@link #getFailures()} will return a map of keys to + * {@link AppSearchResult} objects. * * <p>Alternatively, {@link #getAll()} returns a map of keys to {@link AppSearchResult} objects for * both successes and failures. @@ -97,8 +97,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl } /** - * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all failed - * individual results. + * Returns a {@link Map} of keys mapped to instances of {@link AppSearchResult} for all + * failed individual results. * * <p>The values of the {@link Map} will not be {@code null}. */ @@ -120,7 +120,6 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl /** * Asserts that this {@link AppSearchBatchResult} has no failures. - * * @hide */ public void checkSuccess() { @@ -162,8 +161,6 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * Builder for {@link AppSearchBatchResult} objects. * * <p>Once {@link #build} is called, the instance can no longer be used. - * - * @hide */ public static final class Builder<KeyType, ValueType> { private final Map<KeyType, ValueType> mSuccesses = new ArrayMap<>(); @@ -178,6 +175,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getSuccesses @NonNull public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { @@ -193,6 +191,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getFailures @NonNull public Builder<KeyType, ValueType> setFailure( @NonNull KeyType key, @@ -210,6 +209,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl * * @throws IllegalStateException if the builder has already been used. */ + @SuppressWarnings("MissingGetterMatchingBuilder") // See getAll @NonNull public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java index 440f63341151..b66837d1f679 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java @@ -24,6 +24,8 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import com.android.internal.util.Preconditions; + import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -37,20 +39,19 @@ import java.util.Objects; public final class AppSearchResult<ValueType> implements Parcelable { /** * Result codes from {@link AppSearchSession} methods. - * * @hide */ - @IntDef( - value = { - RESULT_OK, - RESULT_UNKNOWN_ERROR, - RESULT_INTERNAL_ERROR, - RESULT_INVALID_ARGUMENT, - RESULT_IO_ERROR, - RESULT_OUT_OF_SPACE, - RESULT_NOT_FOUND, - RESULT_INVALID_SCHEMA, - }) + @IntDef(value = { + RESULT_OK, + RESULT_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_IO_ERROR, + RESULT_OUT_OF_SPACE, + RESULT_NOT_FOUND, + RESULT_INVALID_SCHEMA, + RESULT_SECURITY_ERROR, + }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} @@ -90,6 +91,9 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** The caller supplied a schema which is invalid or incompatible with the previous schema. */ public static final int RESULT_INVALID_SCHEMA = 7; + /** The caller requested an operation it does not have privileges for. */ + public static final int RESULT_SECURITY_ERROR = 8; + private final @ResultCode int mResultCode; @Nullable private final ValueType mResultValue; @Nullable private final String mErrorMessage; @@ -148,8 +152,8 @@ public final class AppSearchResult<ValueType> implements Parcelable { * * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. The error * message may be {@code null} even if {@link #isSuccess} is {@code false}. See the - * documentation of the particular {@link AppSearchSession} call producing this {@link - * AppSearchResult} for what is returned by {@link #getErrorMessage}. + * documentation of the particular {@link AppSearchSession} call producing this + * {@link AppSearchResult} for what is returned by {@link #getErrorMessage}. */ @Nullable public String getErrorMessage() { @@ -208,8 +212,6 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new successful {@link AppSearchResult}. - * - * @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult( @@ -219,8 +221,6 @@ public final class AppSearchResult<ValueType> implements Parcelable { /** * Creates a new failed {@link AppSearchResult}. - * - * @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> newFailedResult( @@ -228,6 +228,20 @@ public final class AppSearchResult<ValueType> implements Parcelable { return new AppSearchResult<>(resultCode, /*resultValue=*/ null, errorMessage); } + /** + * Creates a new failed {@link AppSearchResult} by a AppSearchResult in another type. + * + * @hide + */ + @NonNull + public static <ValueType> AppSearchResult<ValueType> newFailedResult( + @NonNull AppSearchResult<?> otherFailedResult) { + Preconditions.checkState(!otherFailedResult.isSuccess(), + "Cannot convert a success result to a failed result"); + return AppSearchResult.newFailedResult( + otherFailedResult.getResultCode(), otherFailedResult.getErrorMessage()); + } + /** @hide */ @NonNull public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult( diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java index 9ea73a9773bc..ce4aad1f0c1e 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java @@ -45,11 +45,13 @@ import java.util.function.Consumer; */ public final class AppSearchSession implements Closeable { private static final String TAG = "AppSearchSession"; + private final String mPackageName; private final String mDatabaseName; @UserIdInt private final int mUserId; private final IAppSearchManager mService; + private boolean mIsMutated = false; private boolean mIsClosed = false; @@ -148,6 +150,7 @@ public final class AppSearchSession implements Closeable { schemasPackageAccessibleBundles, request.isForceOverride(), mUserId, + request.getVersion(), new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { executor.execute(() -> { @@ -176,7 +179,7 @@ public final class AppSearchSession implements Closeable { */ public void getSchema( @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<AppSearchResult<Set<AppSearchSchema>>> callback) { + @NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback) { Objects.requireNonNull(executor); Objects.requireNonNull(callback); Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); @@ -189,14 +192,46 @@ public final class AppSearchSession implements Closeable { public void onResult(AppSearchResult result) { executor.execute(() -> { if (result.isSuccess()) { - List<Bundle> schemaBundles = - (List<Bundle>) result.getResultValue(); - Set<AppSearchSchema> schemas = new ArraySet<>( - schemaBundles.size()); - for (int i = 0; i < schemaBundles.size(); i++) { - schemas.add(new AppSearchSchema(schemaBundles.get(i))); - } - callback.accept(AppSearchResult.newSuccessfulResult(schemas)); + Bundle responseBundle = (Bundle) result.getResultValue(); + GetSchemaResponse response = + new GetSchemaResponse(responseBundle); + callback.accept(AppSearchResult.newSuccessfulResult(response)); + } else { + callback.accept(result); + } + }); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive the namespaces. + */ + public void getNamespaces( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<Set<String>>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); + try { + mService.getNamespaces( + mPackageName, + mDatabaseName, + mUserId, + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> { + if (result.isSuccess()) { + Set<String> namespaces = + new ArraySet<>((List<String>) result.getResultValue()); + callback.accept( + AppSearchResult.newSuccessfulResult(namespaces)); } else { callback.accept(result); } @@ -437,6 +472,7 @@ public final class AppSearchSession implements Closeable { request.getNamespace(), request.getUri(), request.getUsageTimeMillis(), + /*systemUsage=*/ false, mUserId, new IAppSearchResultCallback.Stub() { public void onResult(AppSearchResult result) { @@ -541,6 +577,25 @@ public final class AppSearchSession implements Closeable { } /** + * Gets the storage info for this {@link AppSearchSession} database. + * + * <p>This may take time proportional to the number of documents and may be inefficient to + * call repeatedly. + * + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive the storage info. + */ + public void getStorageInfo( + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<StorageInfo>> callback) { + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed"); + // TODO(b/182909475): Implement getStorageInfo + throw new UnsupportedOperationException(); + } + + /** * Closes the {@link AppSearchSession} to persist all schema and document updates, additions, * and deletes to disk. */ diff --git a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java index fb63e16aa6a8..b30ed50b8240 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java +++ b/apex/appsearch/framework/java/android/app/appsearch/GlobalSearchSession.java @@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.os.RemoteException; +import android.util.Log; import com.android.internal.util.Preconditions; @@ -35,14 +36,15 @@ import java.util.function.Consumer; * <p>Apps can retrieve indexed documents through the {@link #search} API. */ public class GlobalSearchSession implements Closeable { + private static final String TAG = "AppSearchGlobalSearchSe"; - private final IAppSearchManager mService; - + private final String mPackageName; @UserIdInt private final int mUserId; - private boolean mIsClosed = false; + private final IAppSearchManager mService; - private final String mPackageName; + private boolean mIsMutated = false; + private boolean mIsClosed = false; /** * Creates a search session for the client, defined by the {@code userId} and @@ -117,9 +119,66 @@ public class GlobalSearchSession implements Closeable { searchSpec, mUserId); } - /** Closes the {@link GlobalSearchSession}. */ + /** + * Reports that a particular document has been used from a system surface. + * + * <p>See {@link AppSearchSession#reportUsage} for a general description of document usage, as + * well as an API that can be used by the app itself. + * + * <p>Usage reported via this method is accounted separately from usage reported via + * {@link AppSearchSession#reportUsage} and may be accessed using the constants + * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and + * {@link SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}. + * + * @param request The usage reporting request. + * @param executor Executor on which to invoke the callback. + * @param callback Callback to receive errors. If the operation succeeds, the callback will be + * invoked with an {@link AppSearchResult} whose value is {@code null}. The + * callback will be invoked with an {@link AppSearchResult} of + * {@link AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an + * app which is not part of the system. + */ + public void reportSystemUsage( + @NonNull ReportSystemUsageRequest request, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<AppSearchResult<Void>> callback) { + Objects.requireNonNull(request); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + Preconditions.checkState(!mIsClosed, "GlobalSearchSession has already been closed"); + try { + mService.reportUsage( + request.getPackageName(), + request.getDatabaseName(), + request.getNamespace(), + request.getUri(), + request.getUsageTimeMillis(), + /*systemUsage=*/ true, + mUserId, + new IAppSearchResultCallback.Stub() { + public void onResult(AppSearchResult result) { + executor.execute(() -> callback.accept(result)); + } + }); + mIsMutated = true; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Closes the {@link GlobalSearchSession}. Persists all mutations, including usage reports, to + * disk. + */ @Override public void close() { - mIsClosed = true; + if (mIsMutated && !mIsClosed) { + try { + mService.persistToDisk(mUserId); + mIsClosed = true; + } catch (RemoteException e) { + Log.e(TAG, "Unable to close the GlobalSearchSession", e); + } + } } } diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl index ba27762c7dfb..d436488f34f2 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl +++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl @@ -51,6 +51,7 @@ interface IAppSearchManager { in Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, in int userId, + in int schemaVersion, in IAppSearchResultCallback callback); /** @@ -60,8 +61,7 @@ interface IAppSearchManager { * @param databaseName The name of the database to retrieve. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an - * {@link AppSearchResult}<{@link List}<{@link Bundle}>>, where the value are - * AppSearchSchema bundle. + * {@link AppSearchResult}<{@link Bundle}> where the bundle is a GetSchemaResponse. */ void getSchema( in String packageName, @@ -70,6 +70,21 @@ interface IAppSearchManager { in IAppSearchResultCallback callback); /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @param packageName The name of the package that owns the schema. + * @param databaseName The name of the database to retrieve. + * @param userId Id of the calling user + * @param callback {@link IAppSearchResultCallback#onResult} will be called with an + * {@link AppSearchResult}<{@link List}<{@link String}>>. + */ + void getNamespaces( + in String packageName, + in String databaseName, + in int userId, + in IAppSearchResultCallback callback); + + /** * Inserts documents into the index. * * @param packageName The name of the package that owns this document. @@ -190,6 +205,7 @@ interface IAppSearchManager { * @param namespace Namespace the document being used belongs to. * @param uri URI of the document being used. * @param usageTimeMillis The timestamp at which the document was used. + * @param systemUsage Whether the usage was reported by a system app against another app's doc. * @param userId Id of the calling user * @param callback {@link IAppSearchResultCallback#onResult} will be called with an * {@link AppSearchResult}<{@link Void}>. @@ -200,6 +216,7 @@ interface IAppSearchManager { in String namespace, in String uri, in long usageTimeMillis, + in boolean systemUsage, in int userId, in IAppSearchResultCallback callback); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java index 55f0c80b16d0..79b7b7552196 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java @@ -46,7 +46,6 @@ import java.util.Set; */ public final class AppSearchSchema { private static final String SCHEMA_TYPE_FIELD = "schemaType"; - private static final String VERSION_FIELD = "version"; private static final String PROPERTIES_FIELD = "properties"; private final Bundle mBundle; @@ -78,9 +77,10 @@ public final class AppSearchSchema { return mBundle.getString(SCHEMA_TYPE_FIELD, ""); } - /** Returns the version of this {@link AppSearchSchema}. */ + /** @deprecated Use {@link GetSchemaResponse#getVersion()} instead. */ + @Deprecated public @IntRange(from = 0) int getVersion() { - return mBundle.getInt(VERSION_FIELD); + return 0; } /** @@ -115,15 +115,12 @@ public final class AppSearchSchema { if (!getSchemaType().equals(otherSchema.getSchemaType())) { return false; } - if (getVersion() != otherSchema.getVersion()) { - return false; - } return getProperties().equals(otherSchema.getProperties()); } @Override public int hashCode() { - return Objects.hash(getSchemaType(), getVersion(), getProperties()); + return Objects.hash(getSchemaType(), getProperties()); } /** Builder for {@link AppSearchSchema objects}. */ @@ -131,7 +128,6 @@ public final class AppSearchSchema { private final String mSchemaType; private final ArrayList<Bundle> mPropertyBundles = new ArrayList<>(); private final Set<String> mPropertyNames = new ArraySet<>(); - private int mVersion; private boolean mBuilt = false; /** Creates a new {@link AppSearchSchema.Builder}. */ @@ -154,38 +150,13 @@ public final class AppSearchSchema { } /** - * Sets the version number of the {@link AppSearchSchema}. - * - * <p>The {@link AppSearchSession} database can only ever hold documents for one version of - * a {@link AppSearchSchema} type at a time. - * - * <p>Setting a version number that is different from the version number of the schema - * currently stored in AppSearch will result in AppSearch calling the {@link Migrator} - * provided to {@link AppSearchSession#setSchema} to migrate the documents already in - * AppSearch from the previous version to the one set in this request. The version number - * can be updated without any other changes to the schema. - * - * <p>The version number can stay the same, increase, or decrease relative to the current - * version number of the {@link AppSearchSchema} type that is already stored in the {@link - * AppSearchSession} database. - * - * <p>The version number will be updated if the {@link SetSchemaRequest} contains - * backwards-compatible changes or {@link SetSchemaRequest.Builder#setForceOverride} method - * is set to {@code true}. - * - * @param version A non-negative int number represents the version of this {@link - * AppSearchSchema}, default version is 0. - * @throws IllegalStateException if the version is negative or the builder has already been - * used. - * @see AppSearchSession#setSchema - * @see Migrator - * @see SetSchemaRequest.Builder#setMigrator + * @deprecated TODO(b/181887768): This method is a no-op and only exists for dogfooder + * transition. */ + @Deprecated @NonNull public AppSearchSchema.Builder setVersion(@IntRange(from = 0) int version) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkArgumentNonnegative(version); - mVersion = version; return this; } @@ -199,7 +170,6 @@ public final class AppSearchSchema { Preconditions.checkState(!mBuilt, "Builder has already been used"); Bundle bundle = new Bundle(); bundle.putString(AppSearchSchema.SCHEMA_TYPE_FIELD, mSchemaType); - bundle.putInt(AppSearchSchema.VERSION_FIELD, mVersion); bundle.putParcelableArrayList(AppSearchSchema.PROPERTIES_FIELD, mPropertyBundles); mBuilt = true; return new AppSearchSchema(bundle); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java new file mode 100644 index 000000000000..3e693670f7bc --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java @@ -0,0 +1,116 @@ +/* + * Copyright 2021 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 android.app.appsearch; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Bundle; +import android.util.ArraySet; + +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** The response class of {@link AppSearchSession#getSchema} */ +// TODO(b/181887768) extends only for dogfooder transition. */ +public class GetSchemaResponse extends HashSet<AppSearchSchema> { + private static final String VERSION_FIELD = "version"; + private static final String SCHEMAS_FIELD = "schemas"; + + private final Bundle mBundle; + + // TODO(b/181887768) Remove this method once this class no longer extends HashSet. */ + private static Set<AppSearchSchema> getSchemasFromBundle(Bundle bundle) { + ArrayList<Bundle> schemaBundles = bundle.getParcelableArrayList(SCHEMAS_FIELD); + Set<AppSearchSchema> schemas = new ArraySet<>(schemaBundles.size()); + for (int i = 0; i < schemaBundles.size(); i++) { + schemas.add(new AppSearchSchema(schemaBundles.get(i))); + } + return schemas; + } + + GetSchemaResponse(@NonNull Bundle bundle) { + super(getSchemasFromBundle(Preconditions.checkNotNull(bundle))); + mBundle = bundle; + } + + /** + * Returns the {@link Bundle} populated by this builder. + * + * @hide + */ + @NonNull + public Bundle getBundle() { + return mBundle; + } + + /** + * Returns the overall database schema version. + * + * <p>If the database is empty, 0 will be returned. + */ + @IntRange(from = 0) + public int getVersion() { + return mBundle.getInt(VERSION_FIELD); + } + + /** + * Return the schemas most recently successfully provided to {@link AppSearchSession#setSchema}. + */ + @NonNull + public Set<AppSearchSchema> getSchemas() { + return this; + } + + /** Builder for {@link GetSchemaResponse} objects. */ + public static final class Builder { + private int mVersion; + private boolean mBuilt = false; + private final ArrayList<Bundle> mSchemaBundles = new ArrayList<>(); + + /** + * Sets the database overall schema version. + * + * <p>Default version is 0 + */ + @NonNull + public Builder setVersion(@IntRange(from = 0) int version) { + mVersion = version; + return this; + } + + /** Adds one {@link AppSearchSchema} to the schema list. */ + @NonNull + public Builder addSchema(@NonNull AppSearchSchema schema) { + mSchemaBundles.add(schema.getBundle()); + return this; + } + + /** Builds a {@link GetSchemaResponse} object. */ + @NonNull + public GetSchemaResponse build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Bundle bundle = new Bundle(); + bundle.putInt(VERSION_FIELD, mVersion); + bundle.putParcelableArrayList(SCHEMAS_FIELD, mSchemaBundles); + mBuilt = true; + return new GetSchemaResponse(bundle); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java index 5ae9a4186a52..511b42ac8f12 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/Migrator.java @@ -19,8 +19,6 @@ package android.app.appsearch; import android.annotation.NonNull; import android.annotation.WorkerThread; -import com.android.internal.util.Preconditions; - /** * A migrator class to translate {@link GenericDocument} from different version of {@link * AppSearchSchema} @@ -37,37 +35,14 @@ import com.android.internal.util.Preconditions; * documents won't have any observable changes. */ public abstract class Migrator { - private final int mStartVersion; - - /** - * Creates a {@link Migrator} will trigger migration for any version less than the final version - * in the new schema. - */ - public Migrator() { - this(/*startVersion=*/ 0); - } - /** - * Creates a {@link Migrator} with a non-negative start version. - * - * <p>Providing 0 will trigger migration for any version less than the final version in the new - * schema. + * Returns {@code true} if this migrator's source type needs to be migrated to update from + * currentVersion to finalVersion. * - * @param startVersion The migration will be only triggered for those versions greater or equal - * to the given startVersion. + * <p>Migration won't be triggered if currentVersion is equal to finalVersion even if {@link + * #shouldMigrate} return true; */ - public Migrator(int startVersion) { - Preconditions.checkArgumentNonnegative(startVersion); - mStartVersion = startVersion; - } - - /** - * @return {@code True} if the current version need to be migrated. - * @hide - */ - public boolean shouldMigrateToFinalVersion(int currentVersion, int finalVersion) { - return currentVersion >= mStartVersion && currentVersion != finalVersion; - } + public abstract boolean shouldMigrate(int currentVersion, int finalVersion); /** * Migrates {@link GenericDocument} to a newer version of {@link AppSearchSchema}. @@ -75,17 +50,22 @@ public abstract class Migrator { * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a higher * version number than the current {@link AppSearchSchema} saved in AppSearch. * - * <p>This method will be invoked on the background worker thread. + * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link + * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use + * the same URI. + * + * <p>This method will be invoked on the background worker thread provided via {@link + * AppSearchSession#setSchema}. * * @param currentVersion The current version of the document's schema. - * @param targetVersion The final version that documents need to be migrated to. + * @param finalVersion The final version that documents need to be migrated to. * @param document The {@link GenericDocument} need to be translated to new version. * @return A {@link GenericDocument} in new version. */ @WorkerThread @NonNull public abstract GenericDocument onUpgrade( - int currentVersion, int targetVersion, @NonNull GenericDocument document); + int currentVersion, int finalVersion, @NonNull GenericDocument document); /** * Migrates {@link GenericDocument} to an older version of {@link AppSearchSchema}. @@ -93,15 +73,19 @@ public abstract class Migrator { * <p>This method will be invoked only if the {@link SetSchemaRequest} is setting a lower * version number than the current {@link AppSearchSchema} saved in AppSearch. * + * <p>If this {@link Migrator} is provided to cover a compatible schema change via {@link + * AppSearchSession#setSchema}, documents under the old version won't be removed unless you use + * the same URI. + * * <p>This method will be invoked on the background worker thread. * * @param currentVersion The current version of the document's schema. - * @param targetVersion The final version that documents need to be migrated to. + * @param finalVersion The final version that documents need to be migrated to. * @param document The {@link GenericDocument} need to be translated to new version. * @return A {@link GenericDocument} in new version. */ @WorkerThread @NonNull public abstract GenericDocument onDowngrade( - int currentVersion, int targetVersion, @NonNull GenericDocument document); + int currentVersion, int finalVersion, @NonNull GenericDocument document); } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java new file mode 100644 index 000000000000..2e152f89465f --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java @@ -0,0 +1,154 @@ +/* + * Copyright 2021 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 android.app.appsearch; + +import android.annotation.NonNull; + +import com.android.internal.util.Preconditions; + +/** + * A request to report usage of a document owned by another app from a system UI surface. + * + * <p>Usage reported in this way is measured separately from usage reported via {@link + * AppSearchSession#reportUsage}. + * + * <p>See {@link GlobalSearchSession#reportSystemUsage} for a detailed description of usage + * reporting. + */ +public final class ReportSystemUsageRequest { + private final String mPackageName; + private final String mDatabase; + private final String mNamespace; + private final String mUri; + private final long mUsageTimeMillis; + + ReportSystemUsageRequest( + @NonNull String packageName, + @NonNull String database, + @NonNull String namespace, + @NonNull String uri, + long usageTimeMillis) { + mPackageName = Preconditions.checkNotNull(packageName); + mDatabase = Preconditions.checkNotNull(database); + mNamespace = Preconditions.checkNotNull(namespace); + mUri = Preconditions.checkNotNull(uri); + mUsageTimeMillis = usageTimeMillis; + } + + /** Returns the package name of the app which owns the document that was used. */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** Returns the database in which the document that was used resides. */ + @NonNull + public String getDatabaseName() { + return mDatabase; + } + + /** Returns the namespace of the document that was used. */ + @NonNull + public String getNamespace() { + return mNamespace; + } + + /** Returns the URI of document that was used. */ + @NonNull + public String getUri() { + return mUri; + } + + /** + * Returns the timestamp in milliseconds of the usage report (the time at which the document was + * used). + * + * <p>The value is in the {@link System#currentTimeMillis} time base. + */ + public long getUsageTimeMillis() { + return mUsageTimeMillis; + } + + /** Builder for {@link ReportSystemUsageRequest} objects. */ + public static final class Builder { + private final String mPackageName; + private final String mDatabase; + private final String mNamespace; + private String mUri; + private Long mUsageTimeMillis; + private boolean mBuilt = false; + + /** Creates a {@link ReportSystemUsageRequest.Builder} instance. */ + public Builder( + @NonNull String packageName, @NonNull String database, @NonNull String namespace) { + mPackageName = Preconditions.checkNotNull(packageName); + mDatabase = Preconditions.checkNotNull(database); + mNamespace = Preconditions.checkNotNull(namespace); + } + + /** + * Sets the URI of the document being used. + * + * <p>This field is required. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest.Builder setUri(@NonNull String uri) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(uri); + mUri = uri; + return this; + } + + /** + * Sets the timestamp in milliseconds of the usage report (the time at which the document + * was used). + * + * <p>The value is in the {@link System#currentTimeMillis} time base. + * + * <p>If unset, this defaults to the current timestamp at the time that the {@link + * ReportSystemUsageRequest} is constructed. + * + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest.Builder setUsageTimeMillis(long usageTimeMillis) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mUsageTimeMillis = usageTimeMillis; + return this; + } + + /** + * Builds a new {@link ReportSystemUsageRequest}. + * + * @throws NullPointerException if {@link #setUri} has never been called + * @throws IllegalStateException if the builder has already been used + */ + @NonNull + public ReportSystemUsageRequest build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI"); + if (mUsageTimeMillis == null) { + mUsageTimeMillis = System.currentTimeMillis(); + } + mBuilt = true; + return new ReportSystemUsageRequest( + mPackageName, mDatabase, mNamespace, mUri, mUsageTimeMillis); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java index cb20849dd36f..e66056f0380b 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java @@ -47,6 +47,7 @@ public final class SearchResult { static final String MATCHES_FIELD = "matches"; static final String PACKAGE_NAME_FIELD = "packageName"; static final String DATABASE_NAME_FIELD = "databaseName"; + static final String RANKING_SIGNAL_FIELD = "rankingSignal"; @NonNull private final Bundle mBundle; @@ -131,6 +132,35 @@ public final class SearchResult { return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD)); } + /** + * Returns the ranking signal of the {@link GenericDocument}, according to the ranking strategy + * set in {@link SearchSpec.Builder#setRankingStrategy(int)}. + * + * <p>The meaning of the ranking signal and its value is determined by the selected ranking + * strategy: + * + * <ul> + * <li>{@link SearchSpec#RANKING_STRATEGY_NONE} - this value will be 0 + * <li>{@link SearchSpec#RANKING_STRATEGY_DOCUMENT_SCORE} - the value returned by calling + * {@link GenericDocument#getScore()} on the document returned by {@link #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_CREATION_TIMESTAMP} - the value returned by calling + * {@link GenericDocument#getCreationTimestampMillis()} on the document returned by {@link + * #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_RELEVANCE_SCORE} - an arbitrary double value where a + * higher value means more relevant + * <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} - the number of times usage has been + * reported for the document returned by {@link #getDocument()} + * <li>{@link SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} - the timestamp of the + * most recent usage that has been reported for the document returned by {@link + * #getDocument()} + * </ul> + * + * @return Ranking signal of the document + */ + public double getRankingSignal() { + return mBundle.getDouble(RANKING_SIGNAL_FIELD); + } + /** Builder for {@link SearchResult} objects. */ public static final class Builder { private final Bundle mBundle = new Bundle(); @@ -173,6 +203,14 @@ public final class SearchResult { return this; } + /** Sets the ranking signal of the matched document in this SearchResult. */ + @NonNull + public Builder setRankingSignal(double rankingSignal) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putDouble(RANKING_SIGNAL_FIELD, rankingSignal); + return this; + } + /** * Constructs a new {@link SearchResult}. * diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java index 7888c8d78cd8..19d94305b3da 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java @@ -19,6 +19,7 @@ package android.app.appsearch; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.app.appsearch.exceptions.IllegalSearchSpecException; import android.os.Bundle; import android.util.ArrayMap; @@ -58,6 +59,8 @@ public final class SearchSpec { static final String SNIPPET_COUNT_PER_PROPERTY_FIELD = "snippetCountPerProperty"; static final String MAX_SNIPPET_FIELD = "maxSnippet"; static final String PROJECTION_TYPE_PROPERTY_PATHS_FIELD = "projectionTypeFieldMasks"; + static final String RESULT_GROUPING_TYPE_FLAGS = "resultGroupingTypeFlags"; + static final String RESULT_GROUPING_LIMIT = "resultGroupingLimit"; /** @hide */ public static final int DEFAULT_NUM_PER_PAGE = 10; @@ -107,7 +110,9 @@ public final class SearchSpec { RANKING_STRATEGY_CREATION_TIMESTAMP, RANKING_STRATEGY_RELEVANCE_SCORE, RANKING_STRATEGY_USAGE_COUNT, - RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP + RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP, + RANKING_STRATEGY_SYSTEM_USAGE_COUNT, + RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP, }) @Retention(RetentionPolicy.SOURCE) public @interface RankingStrategy {} @@ -120,10 +125,14 @@ public final class SearchSpec { public static final int RANKING_STRATEGY_CREATION_TIMESTAMP = 2; /** Ranked by document relevance score. */ public static final int RANKING_STRATEGY_RELEVANCE_SCORE = 3; - /** Ranked by number of usages. */ + /** Ranked by number of usages, as reported by the app. */ public static final int RANKING_STRATEGY_USAGE_COUNT = 4; - /** Ranked by timestamp of last usage. */ + /** Ranked by timestamp of last usage, as reported by the app. */ public static final int RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP = 5; + /** Ranked by number of usages from a system UI surface. */ + public static final int RANKING_STRATEGY_SYSTEM_USAGE_COUNT = 6; + /** Ranked by timestamp of last usage from a system UI surface. */ + public static final int RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP = 7; /** * Order for query result. @@ -141,6 +150,28 @@ public final class SearchSpec { /** Search results will be returned in an ascending order. */ public static final int ORDER_ASCENDING = 1; + /** + * Grouping type for result limits. + * + * @hide + */ + @IntDef( + flag = true, + value = {GROUPING_TYPE_PER_PACKAGE, GROUPING_TYPE_PER_NAMESPACE}) + @Retention(RetentionPolicy.SOURCE) + public @interface GroupingType {} + + /** + * Results should be grouped together by package for the purpose of enforcing a limit on the + * number of results returned per package. + */ + public static final int GROUPING_TYPE_PER_PACKAGE = 0b01; + /** + * Results should be grouped together by namespace for the purpose of enforcing a limit on the + * number of results returned per namespace. + */ + public static final int GROUPING_TYPE_PER_NAMESPACE = 0b10; + private final Bundle mBundle; /** @hide */ @@ -259,6 +290,24 @@ public final class SearchSpec { return typePropertyPathsMap; } + /** + * Get the type of grouping limit to apply, or 0 if {@link Builder#setResultGrouping} was not + * called. + */ + public @GroupingType int getResultGroupingTypeFlags() { + return mBundle.getInt(RESULT_GROUPING_TYPE_FLAGS); + } + + /** + * Get the maximum number of results to return for each group. + * + * @return the maximum number of results to return for each group or Integer.MAX_VALUE if {@link + * Builder#setResultGrouping(int, int)} was not called. + */ + public int getResultGroupingLimit() { + return mBundle.getInt(RESULT_GROUPING_LIMIT, Integer.MAX_VALUE); + } + /** Builder for {@link SearchSpec objects}. */ public static final class Builder { @@ -391,7 +440,7 @@ public final class SearchSpec { Preconditions.checkArgumentInRange( rankingStrategy, RANKING_STRATEGY_NONE, - RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP, + RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP, "Result ranking strategy"); mBundle.putInt(RANKING_STRATEGY_FIELD, rankingStrategy); return this; @@ -549,6 +598,34 @@ public final class SearchSpec { } /** + * Set the maximum number of results to return for each group, where groups are defined by + * grouping type. + * + * <p>Calling this method will override any previous calls. So calling + * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 7) and then calling + * setResultGrouping(GROUPING_TYPE_PER_PACKAGE, 2) will result in only the latter, a limit + * of two results per package, being applied. Or calling setResultGrouping + * (GROUPING_TYPE_PER_PACKAGE, 1) and then calling setResultGrouping + * (GROUPING_TYPE_PER_PACKAGE | GROUPING_PER_NAMESPACE, 5) will result in five results per + * package per namespace. + * + * @param groupingTypeFlags One or more combination of grouping types. + * @param limit Number of results to return per {@code groupingTypeFlags}. + * @throws IllegalArgumentException if groupingTypeFlags is zero. + */ + // Individual parameters available from getResultGroupingTypeFlags and + // getResultGroupingLimit + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setResultGrouping(@GroupingType int groupingTypeFlags, int limit) { + Preconditions.checkState( + groupingTypeFlags != 0, "Result grouping type cannot be zero."); + mBundle.putInt(RESULT_GROUPING_TYPE_FLAGS, groupingTypeFlags); + mBundle.putInt(RESULT_GROUPING_LIMIT, limit); + return this; + } + + /** * Constructs a new {@link SearchSpec} from the contents of this builder. * * <p>After calling this method, the builder must no longer be used. diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java index c1eedcd63dbf..e840ffcc69b1 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java @@ -16,6 +16,7 @@ package android.app.appsearch; +import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SuppressLint; import android.util.ArrayMap; @@ -84,18 +85,21 @@ public final class SetSchemaRequest { private final Map<String, Set<PackageIdentifier>> mSchemasVisibleToPackages; private final Map<String, Migrator> mMigrators; private final boolean mForceOverride; + private final int mVersion; SetSchemaRequest( @NonNull Set<AppSearchSchema> schemas, @NonNull Set<String> schemasNotDisplayedBySystem, @NonNull Map<String, Set<PackageIdentifier>> schemasVisibleToPackages, @NonNull Map<String, Migrator> migrators, - boolean forceOverride) { + boolean forceOverride, + int version) { mSchemas = Preconditions.checkNotNull(schemas); mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem); mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages); mMigrators = Preconditions.checkNotNull(migrators); mForceOverride = forceOverride; + mVersion = version; } /** Returns the {@link AppSearchSchema} types that are part of this request. */ @@ -166,6 +170,12 @@ public final class SetSchemaRequest { return mForceOverride; } + /** Returns the database overall schema version. */ + @IntRange(from = 1) + public int getVersion() { + return mVersion; + } + /** * Builder for {@link SetSchemaRequest} objects. * @@ -178,6 +188,7 @@ public final class SetSchemaRequest { new ArrayMap<>(); private final Map<String, Migrator> mMigrators = new ArrayMap<>(); private boolean mForceOverride = false; + private int mVersion = 1; private boolean mBuilt = false; /** @@ -323,6 +334,19 @@ public final class SetSchemaRequest { } /** + * Sets {@link Migrator}s. + * + * @param migrators A {@link Map} of migrators that translate a document from its old + * version to a new incompatible version. + */ + @NonNull + public Builder setMigrators(@NonNull Map<String, Migrator> migrators) { + Preconditions.checkNotNull(migrators); + mMigrators.putAll(migrators); + return this; + } + + /** * Sets whether or not to override the current schema in the {@link AppSearchSession} * database. * @@ -340,6 +364,37 @@ public final class SetSchemaRequest { } /** + * Sets the version number of the overall {@link AppSearchSchema} in the database. + * + * <p>The {@link AppSearchSession} database can only ever hold documents for one version at + * a time. + * + * <p>Setting a version number that is different from the version number currently stored in + * AppSearch will result in AppSearch calling the {@link Migrator}s provided to {@link + * AppSearchSession#setSchema} to migrate the documents already in AppSearch from the + * previous version to the one set in this request. The version number can be updated + * without any other changes to the set of schemas. + * + * <p>The version number can stay the same, increase, or decrease relative to the current + * version number that is already stored in the {@link AppSearchSession} database. + * + * @param version A positive integer representing the version of the entire set of schemas + * represents the version of the whole schema in the {@link AppSearchSession} database, + * default version is 1. + * @throws IllegalStateException if the version is negative or the builder has already been + * used. + * @see AppSearchSession#setSchema + * @see Migrator + * @see SetSchemaRequest.Builder#setMigrator + */ + @NonNull + public Builder setVersion(@IntRange(from = 1) int version) { + Preconditions.checkArgument(version >= 1, "Version must be a positive number."); + mVersion = version; + return this; + } + + /** * Builds a new {@link SetSchemaRequest} object. * * @throws IllegalArgumentException if schema types were referenced, but the corresponding @@ -372,7 +427,8 @@ public final class SetSchemaRequest { mSchemasNotDisplayedBySystem, mSchemasVisibleToPackages, mMigrators, - mForceOverride); + mForceOverride, + mVersion); } } } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java new file mode 100644 index 000000000000..dc04cf3068ce --- /dev/null +++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021 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 android.app.appsearch; + +import android.annotation.NonNull; +import android.os.Bundle; + +import com.android.internal.util.Preconditions; + +/** The response class of {@code AppSearchSession#getStorageInfo}. */ +public class StorageInfo { + + private static final String SIZE_BYTES_FIELD = "sizeBytes"; + private static final String ALIVE_DOCUMENTS_COUNT = "aliveDocumentsCount"; + private static final String ALIVE_NAMESPACES_COUNT = "aliveNamespacesCount"; + + private final Bundle mBundle; + + StorageInfo(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + } + + /** + * Returns the {@link Bundle} populated by this builder. + * + * @hide + */ + @NonNull + public Bundle getBundle() { + return mBundle; + } + + /** Returns the estimated size of the session's database in bytes. */ + public long getSizeBytes() { + return mBundle.getLong(SIZE_BYTES_FIELD); + } + + /** + * Returns the number of alive documents in the current session. + * + * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as + * set in {@link GenericDocument.Builder#setTtlMillis}. + */ + public int getAliveDocumentsCount() { + return mBundle.getInt(ALIVE_DOCUMENTS_COUNT); + } + + /** + * Returns the number of namespaces that have at least one alive document in the current + * session's database. + * + * <p>Alive documents are documents that haven't been deleted and haven't exceeded the ttl as + * set in {@link GenericDocument.Builder#setTtlMillis}. + */ + public int getAliveNamespacesCount() { + return mBundle.getInt(ALIVE_NAMESPACES_COUNT); + } + + /** Builder for {@link StorageInfo} objects. */ + public static final class Builder { + private final Bundle mBundle = new Bundle(); + private boolean mBuilt = false; + + /** Sets the size in bytes. */ + @NonNull + public StorageInfo.Builder setSizeBytes(long sizeBytes) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putLong(SIZE_BYTES_FIELD, sizeBytes); + return this; + } + + /** Sets the number of alive documents. */ + @NonNull + public StorageInfo.Builder setAliveDocumentsCount(int numAliveDocuments) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putInt(ALIVE_DOCUMENTS_COUNT, numAliveDocuments); + return this; + } + + /** Sets the number of alive namespaces. */ + @NonNull + public StorageInfo.Builder setAliveNamespacesCount(int numAliveNamespaces) { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBundle.putInt(ALIVE_NAMESPACES_COUNT, numAliveNamespaces); + return this; + } + + /** Builds a {@link StorageInfo} object. */ + @NonNull + public StorageInfo build() { + Preconditions.checkState(!mBuilt, "Builder has already been used"); + mBuilt = true; + return new StorageInfo(mBundle); + } + } +} diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java index fae8ad48e61c..32d7e043e954 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/SchemaMigrationUtil.java @@ -20,12 +20,11 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.Migrator; +import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.exceptions.AppSearchException; import android.util.ArrayMap; import android.util.ArraySet; -import android.util.Log; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -36,83 +35,70 @@ import java.util.Set; * @hide */ public final class SchemaMigrationUtil { - private static final String TAG = "AppSearchMigrateUtil"; - private SchemaMigrationUtil() {} - /** - * Finds out which incompatible schema type won't be migrated by comparing its current and final - * version number. - */ + /** Returns all active {@link Migrator}s that need to be triggered in this migration. */ @NonNull - public static Set<String> getUnmigratedIncompatibleTypes( - @NonNull Set<String> incompatibleSchemaTypes, + public static Map<String, Migrator> getActiveMigrators( + @NonNull Set<AppSearchSchema> existingSchemas, @NonNull Map<String, Migrator> migrators, - @NonNull Map<String, Integer> currentVersionMap, - @NonNull Map<String, Integer> finalVersionMap) - throws AppSearchException { - Set<String> unmigratedSchemaTypes = new ArraySet<>(); - for (String unmigratedSchemaType : incompatibleSchemaTypes) { - Integer currentVersion = currentVersionMap.get(unmigratedSchemaType); - Integer finalVersion = finalVersionMap.get(unmigratedSchemaType); - if (currentVersion == null) { - // impossible, we have done something wrong. - throw new AppSearchException( - AppSearchResult.RESULT_UNKNOWN_ERROR, - "Cannot find the current version number for schema type: " - + unmigratedSchemaType); - } - if (finalVersion == null) { - // The schema doesn't exist in the SetSchemaRequest. - unmigratedSchemaTypes.add(unmigratedSchemaType); - continue; - } - // we don't have migrator or won't trigger migration for this schema type. - Migrator migrator = migrators.get(unmigratedSchemaType); - if (migrator == null - || !migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion)) { - unmigratedSchemaTypes.add(unmigratedSchemaType); + int currentVersion, + int finalVersion) { + if (currentVersion == finalVersion) { + return Collections.emptyMap(); + } + Set<String> existingTypes = new ArraySet<>(existingSchemas.size()); + for (AppSearchSchema schema : existingSchemas) { + existingTypes.add(schema.getSchemaType()); + } + + Map<String, Migrator> activeMigrators = new ArrayMap<>(); + for (Map.Entry<String, Migrator> entry : migrators.entrySet()) { + // The device contains the source type, and we should trigger migration for the type. + String schemaType = entry.getKey(); + Migrator migrator = entry.getValue(); + if (existingTypes.contains(schemaType) + && migrator.shouldMigrate(currentVersion, finalVersion)) { + activeMigrators.put(schemaType, migrator); } } - return Collections.unmodifiableSet(unmigratedSchemaTypes); + return activeMigrators; } /** - * Triggers upgrade or downgrade migration for the given schema type if its version stored in - * AppSearch is different with the version in the request. - * - * @return {@code True} if we trigger the migration for the given type. + * Checks the setSchema() call won't delete any types or has incompatible types after all {@link + * Migrator} has been triggered.. */ - public static boolean shouldTriggerMigration( - @NonNull String schemaType, - @NonNull Migrator migrator, - @NonNull Map<String, Integer> currentVersionMap, - @NonNull Map<String, Integer> finalVersionMap) + public static void checkDeletedAndIncompatibleAfterMigration( + @NonNull SetSchemaResponse setSchemaResponse, @NonNull Set<String> activeMigrators) throws AppSearchException { - Integer currentVersion = currentVersionMap.get(schemaType); - Integer finalVersion = finalVersionMap.get(schemaType); - if (currentVersion == null) { - Log.d(TAG, "The SchemaType: " + schemaType + " not present in AppSearch."); - return false; - } - if (finalVersion == null) { - throw new AppSearchException( - AppSearchResult.RESULT_INVALID_ARGUMENT, - "Receive a migrator for schema type : " - + schemaType - + ", but the schema doesn't exist in the request."); - } - return migrator.shouldMigrateToFinalVersion(currentVersion, finalVersion); + Set<String> unmigratedIncompatibleTypes = + new ArraySet<>(setSchemaResponse.getIncompatibleTypes()); + unmigratedIncompatibleTypes.removeAll(activeMigrators); + + Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes()); + unmigratedDeletedTypes.removeAll(activeMigrators); + + // check if there are any unmigrated incompatible types or deleted types. If there + // are, we will getActiveMigratorsthrow an exception. That's the only case we + // swallowed in the AppSearchImpl#setSchema(). + // Since the force override is false, the schema will not have been set if there are + // any incompatible or deleted types. + checkDeletedAndIncompatible(unmigratedDeletedTypes, unmigratedIncompatibleTypes); } - /** Builds a Map of SchemaType and its version of given set of {@link AppSearchSchema}. */ - @NonNull - public static Map<String, Integer> buildVersionMap( - @NonNull Collection<AppSearchSchema> schemas) { - Map<String, Integer> currentVersionMap = new ArrayMap<>(schemas.size()); - for (AppSearchSchema currentSchema : schemas) { - currentVersionMap.put(currentSchema.getSchemaType(), currentSchema.getVersion()); + /** Checks the setSchema() call won't delete any types or has incompatible types. */ + public static void checkDeletedAndIncompatible( + @NonNull Set<String> deletedTypes, @NonNull Set<String> incompatibleTypes) + throws AppSearchException { + if (deletedTypes.size() > 0 || incompatibleTypes.size() > 0) { + String newMessage = + "Schema is incompatible." + + "\n Deleted types: " + + deletedTypes + + "\n Incompatible types: " + + incompatibleTypes; + throw new AppSearchException(AppSearchResult.RESULT_INVALID_SCHEMA, newMessage); } - return currentVersionMap; } } diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java index 28069743d957..6e3fb82ba213 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java @@ -24,6 +24,7 @@ import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.IAppSearchBatchResultCallback; import android.app.appsearch.IAppSearchManager; import android.app.appsearch.IAppSearchResultCallback; @@ -96,6 +97,7 @@ public class AppSearchManagerService extends SystemService { @NonNull Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, @UserIdInt int userId, + int schemaVersion, @NonNull IAppSearchResultCallback callback) { Preconditions.checkNotNull(packageName); Preconditions.checkNotNull(databaseName); @@ -129,7 +131,8 @@ public class AppSearchManagerService extends SystemService { schemas, schemasNotDisplayedBySystem, schemasPackageAccessible, - forceOverride); + forceOverride, + schemaVersion); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { @@ -156,13 +159,35 @@ public class AppSearchManagerService extends SystemService { verifyCallingPackage(callingUid, packageName); AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - List<AppSearchSchema> schemas = impl.getSchema(packageName, databaseName); - List<Bundle> schemaBundles = new ArrayList<>(schemas.size()); - for (int i = 0; i < schemas.size(); i++) { - schemaBundles.add(schemas.get(i).getBundle()); - } + GetSchemaResponse response = impl.getSchema(packageName, databaseName); invokeCallbackOnResult( - callback, AppSearchResult.newSuccessfulResult(schemaBundles)); + callback, AppSearchResult.newSuccessfulResult(response.getBundle())); + } catch (Throwable t) { + invokeCallbackOnError(callback, t); + } finally { + Binder.restoreCallingIdentity(callingIdentity); + } + } + + @Override + public void getNamespaces( + @NonNull String packageName, + @NonNull String databaseName, + @UserIdInt int userId, + @NonNull IAppSearchResultCallback callback) { + Preconditions.checkNotNull(packageName); + Preconditions.checkNotNull(databaseName); + Preconditions.checkNotNull(callback); + int callingUid = Binder.getCallingUidOrThrow(); + int callingUserId = handleIncomingUser(userId, callingUid); + final long callingIdentity = Binder.clearCallingIdentity(); + try { + verifyUserUnlocked(callingUserId); + verifyCallingPackage(callingUid, packageName); + AppSearchImpl impl = + mImplInstanceManager.getAppSearchImpl(callingUserId); + List<String> namespaces = impl.getNamespaces(packageName, databaseName); + invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(namespaces)); } catch (Throwable t) { invokeCallbackOnError(callback, t); } finally { @@ -380,6 +405,7 @@ public class AppSearchManagerService extends SystemService { @NonNull String namespace, @NonNull String uri, long usageTimeMillis, + boolean systemUsage, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { Objects.requireNonNull(databaseName); @@ -391,9 +417,16 @@ public class AppSearchManagerService extends SystemService { final long callingIdentity = Binder.clearCallingIdentity(); try { verifyUserUnlocked(callingUserId); + + if (systemUsage) { + // TODO(b/183031844): Validate that the call comes from the system + } + AppSearchImpl impl = mImplInstanceManager.getAppSearchImpl(callingUserId); - impl.reportUsage(packageName, databaseName, namespace, uri, usageTimeMillis); + impl.reportUsage( + packageName, databaseName, namespace, uri, + usageTimeMillis, systemUsage); invokeCallbackOnResult( callback, AppSearchResult.newSuccessfulResult(/*result=*/ null)); } catch (Throwable t) { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java index ad94a0a57d27..78474295078a 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -24,6 +24,7 @@ import android.annotation.UserIdInt; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; @@ -73,6 +74,9 @@ public class VisibilityStore { /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */ private static final String VISIBILITY_TYPE = "VisibilityType"; + /** Version for the visibility schema */ + private static final int SCHEMA_VERSION = 0; + /** * Property that holds the list of platform-hidden schemas, as part of the visibility settings. */ @@ -218,11 +222,10 @@ public class VisibilityStore { * @throws AppSearchException AppSearchException on AppSearchImpl error. */ public void initialize() throws AppSearchException { - List<AppSearchSchema> schemas = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME); + GetSchemaResponse getSchemaResponse = mAppSearchImpl.getSchema(PACKAGE_NAME, DATABASE_NAME); boolean hasVisibilityType = false; boolean hasPackageAccessibleType = false; - for (int i = 0; i < schemas.size(); i++) { - AppSearchSchema schema = schemas.get(i); + for (AppSearchSchema schema : getSchemaResponse.getSchemas()) { if (schema.getSchemaType().equals(VISIBILITY_TYPE)) { hasVisibilityType = true; } else if (schema.getSchemaType().equals(PACKAGE_ACCESSIBLE_TYPE)) { @@ -242,7 +245,8 @@ public class VisibilityStore { Arrays.asList(VISIBILITY_SCHEMA, PACKAGE_ACCESSIBLE_SCHEMA), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ SCHEMA_VERSION); } // Populate visibility settings set diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 5e8760ec35c2..de9d609ea17b 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java @@ -23,10 +23,12 @@ import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByUriRequest; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PackageIdentifier; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.os.Bundle; @@ -51,6 +53,7 @@ import com.google.android.icing.IcingSearchEngine; import com.google.android.icing.proto.DeleteByQueryResultProto; import com.google.android.icing.proto.DeleteResultProto; import com.google.android.icing.proto.DocumentProto; +import com.google.android.icing.proto.DocumentStorageInfoProto; import com.google.android.icing.proto.GetAllNamespacesResultProto; import com.google.android.icing.proto.GetOptimizeInfoResultProto; import com.google.android.icing.proto.GetResultProto; @@ -58,8 +61,10 @@ import com.google.android.icing.proto.GetResultSpecProto; import com.google.android.icing.proto.GetSchemaResultProto; import com.google.android.icing.proto.IcingSearchEngineOptions; import com.google.android.icing.proto.InitializeResultProto; +import com.google.android.icing.proto.NamespaceStorageInfoProto; import com.google.android.icing.proto.OptimizeResultProto; import com.google.android.icing.proto.PersistToDiskResultProto; +import com.google.android.icing.proto.PersistType; import com.google.android.icing.proto.PropertyConfigProto; import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.PutResultProto; @@ -73,6 +78,7 @@ import com.google.android.icing.proto.SearchResultProto; import com.google.android.icing.proto.SearchSpecProto; import com.google.android.icing.proto.SetSchemaResultProto; import com.google.android.icing.proto.StatusProto; +import com.google.android.icing.proto.StorageInfoResultProto; import com.google.android.icing.proto.TypePropertyMask; import com.google.android.icing.proto.UsageReport; @@ -295,11 +301,12 @@ public final class AppSearchImpl implements Closeable { * @param schemasPackageAccessible Schema types that are visible to the specified packages. * @param forceOverride Whether to force-apply the schema even if it is incompatible. Documents * which do not comply with the new schema will be deleted. + * @param version The overall version number of the request. + * @return The response contains deleted schema types and incompatible schema types of this + * call. * @throws AppSearchException On IcingSearchEngine error. If the status code is * FAILED_PRECONDITION for the incompatible change, the exception will be converted to the * SetSchemaResponse. - * @return The response contains deleted schema types and incompatible schema types of this - * call. */ @NonNull public SetSchemaResponse setSchema( @@ -308,7 +315,8 @@ public final class AppSearchImpl implements Closeable { @NonNull List<AppSearchSchema> schemas, @NonNull List<String> schemasNotPlatformSurfaceable, @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible, - boolean forceOverride) + boolean forceOverride, + int version) throws AppSearchException { mReadWriteLock.writeLock().lock(); try { @@ -320,7 +328,7 @@ public final class AppSearchImpl implements Closeable { for (int i = 0; i < schemas.size(); i++) { AppSearchSchema schema = schemas.get(i); SchemaTypeConfigProto schemaTypeProto = - SchemaToProtoConverter.toSchemaTypeConfigProto(schema); + SchemaToProtoConverter.toSchemaTypeConfigProto(schema, version); newSchemaBuilder.addTypes(schemaTypeProto); } @@ -394,8 +402,8 @@ public final class AppSearchImpl implements Closeable { * @throws AppSearchException on IcingSearchEngine error. */ @NonNull - public List<AppSearchSchema> getSchema( - @NonNull String packageName, @NonNull String databaseName) throws AppSearchException { + public GetSchemaResponse getSchema(@NonNull String packageName, @NonNull String databaseName) + throws AppSearchException { mReadWriteLock.readLock().lock(); try { throwIfClosedLocked(); @@ -403,7 +411,12 @@ public final class AppSearchImpl implements Closeable { SchemaProto fullSchema = getSchemaProtoLocked(); String prefix = createPrefix(packageName, databaseName); - List<AppSearchSchema> result = new ArrayList<>(); + GetSchemaResponse.Builder responseBuilder = new GetSchemaResponse.Builder(); + if (!fullSchema.getTypesList().isEmpty()) { + // TODO(b/183050495) find a place to store the version for the database, rather + // than read from a schema. + responseBuilder.setVersion(fullSchema.getTypes(0).getVersion()); + } for (int i = 0; i < fullSchema.getTypesCount(); i++) { String typePrefix = getPrefix(fullSchema.getTypes(i).getSchemaType()); if (!prefix.equals(typePrefix)) { @@ -431,9 +444,44 @@ public final class AppSearchImpl implements Closeable { AppSearchSchema schema = SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder); - result.add(schema); + responseBuilder.addSchema(schema); + } + return responseBuilder.build(); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + /** + * Retrieves the list of namespaces with at least one document for this package name, database. + * + * <p>This method belongs to query group. + * + * @param packageName Package name that owns this schema + * @param databaseName The name of the database where this schema lives. + * @throws AppSearchException on IcingSearchEngine error. + */ + @NonNull + public List<String> getNamespaces(@NonNull String packageName, @NonNull String databaseName) + throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + // We can't just use mNamespaceMap here because we have no way to prune namespaces from + // mNamespaceMap when they have no more documents (e.g. after setting schema to empty or + // using deleteByQuery). + GetAllNamespacesResultProto getAllNamespacesResultProto = + mIcingSearchEngineLocked.getAllNamespaces(); + checkSuccess(getAllNamespacesResultProto.getStatus()); + String prefix = createPrefix(packageName, databaseName); + List<String> results = new ArrayList<>(); + for (int i = 0; i < getAllNamespacesResultProto.getNamespacesCount(); i++) { + String prefixedNamespace = getAllNamespacesResultProto.getNamespaces(i); + if (prefixedNamespace.startsWith(prefix)) { + results.add(prefixedNamespace.substring(prefix.length())); + } } - return result; + return results; } finally { mReadWriteLock.readLock().unlock(); } @@ -749,6 +797,18 @@ public final class AppSearchImpl implements Closeable { ResultSpecProto.Builder resultSpecBuilder = SearchSpecToProtoConverter.toResultSpecProto(searchSpec).toBuilder(); + int groupingType = searchSpec.getResultGroupingTypeFlags(); + if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0 + && (groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) { + addPerPackagePerNamespaceResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_PACKAGE) != 0) { + addPerPackageResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } else if ((groupingType & SearchSpec.GROUPING_TYPE_PER_NAMESPACE) != 0) { + addPerNamespaceResultGroupingsLocked( + resultSpecBuilder, prefixes, searchSpec.getResultGroupingLimit()); + } rewriteResultSpecForPrefixesLocked(resultSpecBuilder, prefixes, allowedPrefixedSchemas); ScoringSpecProto scoringSpec = SearchSpecToProtoConverter.toScoringSpecProto(searchSpec); @@ -810,19 +870,24 @@ public final class AppSearchImpl implements Closeable { @NonNull String databaseName, @NonNull String namespace, @NonNull String uri, - long usageTimestampMillis) + long usageTimestampMillis, + boolean systemUsage) throws AppSearchException { mReadWriteLock.writeLock().lock(); try { throwIfClosedLocked(); String prefixedNamespace = createPrefix(packageName, databaseName) + namespace; + UsageReport.UsageType usageType = + systemUsage + ? UsageReport.UsageType.USAGE_TYPE2 + : UsageReport.UsageType.USAGE_TYPE1; UsageReport report = UsageReport.newBuilder() .setDocumentNamespace(prefixedNamespace) .setDocumentUri(uri) .setUsageTimestampMs(usageTimestampMillis) - .setUsageType(UsageReport.UsageType.USAGE_TYPE1) + .setUsageType(usageType) .build(); ReportUsageResultProto result = mIcingSearchEngineLocked.reportUsage(report); @@ -919,6 +984,124 @@ public final class AppSearchImpl implements Closeable { } } + /** Estimates the storage usage info for a specific package. */ + @NonNull + public StorageInfo getStorageInfoForPackage(@NonNull String packageName) + throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + + Map<String, Set<String>> packageToDatabases = getPackageToDatabases(); + Set<String> databases = packageToDatabases.get(packageName); + if (databases == null) { + // Package doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + + // Accumulate all the namespaces we're interested in. + Set<String> wantedPrefixedNamespaces = new ArraySet<>(); + for (String database : databases) { + Set<String> prefixedNamespaces = + mNamespaceMapLocked.get(createPrefix(packageName, database)); + if (prefixedNamespaces != null) { + wantedPrefixedNamespaces.addAll(prefixedNamespaces); + } + } + if (wantedPrefixedNamespaces.isEmpty()) { + return new StorageInfo.Builder().build(); + } + + return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + /** Estimates the storage usage info for a specific database in a package. */ + @NonNull + public StorageInfo getStorageInfoForDatabase( + @NonNull String packageName, @NonNull String databaseName) throws AppSearchException { + mReadWriteLock.readLock().lock(); + try { + throwIfClosedLocked(); + + Map<String, Set<String>> packageToDatabases = getPackageToDatabases(); + Set<String> databases = packageToDatabases.get(packageName); + if (databases == null) { + // Package doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + if (!databases.contains(databaseName)) { + // Database doesn't exist, no storage info to report + return new StorageInfo.Builder().build(); + } + + Set<String> wantedPrefixedNamespaces = + mNamespaceMapLocked.get(createPrefix(packageName, databaseName)); + if (wantedPrefixedNamespaces == null || wantedPrefixedNamespaces.isEmpty()) { + return new StorageInfo.Builder().build(); + } + + return getStorageInfoForNamespacesLocked(wantedPrefixedNamespaces); + } finally { + mReadWriteLock.readLock().unlock(); + } + } + + @GuardedBy("mReadWriteLock") + @NonNull + private StorageInfo getStorageInfoForNamespacesLocked(@NonNull Set<String> prefixedNamespaces) + throws AppSearchException { + StorageInfoResultProto storageInfoResult = mIcingSearchEngineLocked.getStorageInfo(); + checkSuccess(storageInfoResult.getStatus()); + if (!storageInfoResult.hasStorageInfo() + || !storageInfoResult.getStorageInfo().hasDocumentStorageInfo()) { + return new StorageInfo.Builder().build(); + } + long totalStorageSize = storageInfoResult.getStorageInfo().getTotalStorageSize(); + + DocumentStorageInfoProto documentStorageInfo = + storageInfoResult.getStorageInfo().getDocumentStorageInfo(); + int totalDocuments = + documentStorageInfo.getNumAliveDocuments() + + documentStorageInfo.getNumExpiredDocuments(); + + if (totalStorageSize == 0 || totalDocuments == 0) { + // Maybe we can exit early and also avoid a divide by 0 error. + return new StorageInfo.Builder().build(); + } + + // Accumulate stats across the package's namespaces. + int aliveDocuments = 0; + int expiredDocuments = 0; + int aliveNamespaces = 0; + List<NamespaceStorageInfoProto> namespaceStorageInfos = + documentStorageInfo.getNamespaceStorageInfoList(); + for (int i = 0; i < namespaceStorageInfos.size(); i++) { + NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfos.get(i); + // The namespace from icing lib is already the prefixed format + if (prefixedNamespaces.contains(namespaceStorageInfo.getNamespace())) { + if (namespaceStorageInfo.getNumAliveDocuments() > 0) { + aliveNamespaces++; + aliveDocuments += namespaceStorageInfo.getNumAliveDocuments(); + } + expiredDocuments += namespaceStorageInfo.getNumExpiredDocuments(); + } + } + int namespaceDocuments = aliveDocuments + expiredDocuments; + + // Since we don't have the exact size of all the documents, we do an estimation. Note + // that while the total storage takes into account schema, index, etc. in addition to + // documents, we'll only calculate the percentage based on number of documents a + // client has. + return new StorageInfo.Builder() + .setSizeBytes((long) (namespaceDocuments * 1.0 / totalDocuments * totalStorageSize)) + .setAliveDocumentsCount(aliveDocuments) + .setAliveNamespacesCount(aliveNamespaces) + .build(); + } + /** * Persists all update/delete requests to the disk. * @@ -937,7 +1120,7 @@ public final class AppSearchImpl implements Closeable { throwIfClosedLocked(); PersistToDiskResultProto persistToDiskResultProto = - mIcingSearchEngineLocked.persistToDisk(); + mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL); checkSuccess(persistToDiskResultProto.getStatus()); } finally { mReadWriteLock.writeLock().unlock(); @@ -1275,6 +1458,153 @@ public final class AppSearchImpl implements Closeable { .addAllTypePropertyMasks(prefixedTypePropertyMasks); } + /** + * Adds result groupings for each namespace in each package being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerPackagePerNamespaceResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Create a map for package+namespace to prefixedNamespaces. This is NOT necessarily the + // same as the list of namespaces. If one package has multiple databases, each with the same + // namespace, then those should be grouped together. + Map<String, List<String>> packageAndNamespaceToNamespaces = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + String packageName = getPackageName(prefix); + // Create a new prefix without the database name. This will allow us to group namespaces + // that have the same name and package but a different database name together. + String emptyDatabasePrefix = createPrefix(packageName, /*databaseName*/ ""); + for (String prefixedNamespace : prefixedNamespaces) { + String namespace; + try { + namespace = removePrefix(prefixedNamespace); + } catch (AppSearchException e) { + // This should never happen. Skip this namespace if it does. + Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); + continue; + } + String emptyDatabasePrefixedNamespace = emptyDatabasePrefix + namespace; + List<String> namespaceList = + packageAndNamespaceToNamespaces.get(emptyDatabasePrefixedNamespace); + if (namespaceList == null) { + namespaceList = new ArrayList<>(); + packageAndNamespaceToNamespaces.put( + emptyDatabasePrefixedNamespace, namespaceList); + } + namespaceList.add(prefixedNamespace); + } + } + + for (List<String> namespaces : packageAndNamespaceToNamespaces.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(namespaces) + .setMaxResults(maxNumResults)); + } + } + + /** + * Adds result groupings for each package being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerPackageResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Build up a map of package to namespaces. + Map<String, List<String>> packageToNamespacesMap = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + String packageName = getPackageName(prefix); + List<String> packageNamespaceList = packageToNamespacesMap.get(packageName); + if (packageNamespaceList == null) { + packageNamespaceList = new ArrayList<>(); + packageToNamespacesMap.put(packageName, packageNamespaceList); + } + packageNamespaceList.addAll(prefixedNamespaces); + } + + for (List<String> prefixedNamespaces : packageToNamespacesMap.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(prefixedNamespaces) + .setMaxResults(maxNumResults)); + } + } + + /** + * Adds result groupings for each namespace being queried for. + * + * <p>This method should be only called in query methods and get the READ lock to keep thread + * safety. + * + * @param resultSpecBuilder ResultSpecs as specified by client + * @param prefixes Prefixes that we should prepend to all our filters + * @param maxNumResults The maximum number of results for each grouping to support. + */ + @GuardedBy("mReadWriteLock") + private void addPerNamespaceResultGroupingsLocked( + @NonNull ResultSpecProto.Builder resultSpecBuilder, + @NonNull Set<String> prefixes, + int maxNumResults) { + Set<String> existingPrefixes = new ArraySet<>(mNamespaceMapLocked.keySet()); + existingPrefixes.retainAll(prefixes); + + // Create a map of namespace to prefixedNamespaces. This is NOT necessarily the + // same as the list of namespaces. If a namespace exists under different packages and/or + // different databases, they should still be grouped together. + Map<String, List<String>> namespaceToPrefixedNamespaces = new ArrayMap<>(); + for (String prefix : existingPrefixes) { + Set<String> prefixedNamespaces = mNamespaceMapLocked.get(prefix); + for (String prefixedNamespace : prefixedNamespaces) { + String namespace; + try { + namespace = removePrefix(prefixedNamespace); + } catch (AppSearchException e) { + // This should never happen. Skip this namespace if it does. + Log.e(TAG, "Prefixed namespace " + prefixedNamespace + " is malformed."); + continue; + } + List<String> groupedPrefixedNamespaces = + namespaceToPrefixedNamespaces.get(namespace); + if (groupedPrefixedNamespaces == null) { + groupedPrefixedNamespaces = new ArrayList<>(); + namespaceToPrefixedNamespaces.put(namespace, groupedPrefixedNamespaces); + } + groupedPrefixedNamespaces.add(prefixedNamespace); + } + } + + for (List<String> namespaces : namespaceToPrefixedNamespaces.values()) { + resultSpecBuilder.addResultGroupings( + ResultSpecProto.ResultGrouping.newBuilder() + .addAllNamespaces(namespaces) + .setMaxResults(maxNumResults)); + } + } + @VisibleForTesting @GuardedBy("mReadWriteLock") SchemaProto getSchemaProtoLocked() throws AppSearchException { diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java index ce1c9f4d4744..800b073fd44f 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverter.java @@ -46,12 +46,13 @@ public final class SchemaToProtoConverter { * SchemaTypeConfigProto}. */ @NonNull - public static SchemaTypeConfigProto toSchemaTypeConfigProto(@NonNull AppSearchSchema schema) { + public static SchemaTypeConfigProto toSchemaTypeConfigProto( + @NonNull AppSearchSchema schema, int version) { Preconditions.checkNotNull(schema); SchemaTypeConfigProto.Builder protoBuilder = SchemaTypeConfigProto.newBuilder() .setSchemaType(schema.getSchemaType()) - .setVersion(schema.getVersion()); + .setVersion(version); List<AppSearchSchema.PropertyConfig> properties = schema.getProperties(); for (int i = 0; i < properties.size(); i++) { PropertyConfigProto propertyProto = toPropertyConfigProto(properties.get(i)); @@ -116,8 +117,7 @@ public final class SchemaToProtoConverter { @NonNull public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) { Preconditions.checkNotNull(proto); - AppSearchSchema.Builder builder = - new AppSearchSchema.Builder(proto.getSchemaType()).setVersion(proto.getVersion()); + AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType()); List<PropertyConfigProto> properties = proto.getPropertiesList(); for (int i = 0; i < properties.size(); i++) { AppSearchSchema.PropertyConfig propertyConfig = toPropertyConfig(properties.get(i)); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java index 1d8db7233a7a..bf7e5334d090 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchResultToProtoConverter.java @@ -87,7 +87,9 @@ public class SearchResultToProtoConverter { GenericDocument document = GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument()); SearchResult.Builder builder = - new SearchResult.Builder(packageName, databaseName).setGenericDocument(document); + new SearchResult.Builder(packageName, databaseName) + .setGenericDocument(document) + .setRankingSignal(proto.getScore()); if (proto.hasSnippet()) { for (int i = 0; i < proto.getSnippet().getEntriesCount(); i++) { SnippetProto.EntryProto entry = proto.getSnippet().getEntries(i); diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java index 3b5e27573aaa..d9e8adb52e5e 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SearchSpecToProtoConverter.java @@ -104,6 +104,10 @@ public final class SearchSpecToProtoConverter { return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_COUNT; case SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP: return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE1_LAST_USED_TIMESTAMP; + case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_COUNT; + case SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP: + return ScoringSpecProto.RankingStrategy.Code.USAGE_TYPE2_LAST_USED_TIMESTAMP; default: throw new IllegalArgumentException( "Invalid result ranking strategy: " + rankingStrategyCode); diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 09522152cefb..58f430b5bd61 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -I723a9d7b5e64329ab25b6d7627f3b2d222c31ac7 +Ie11a0555775a0ab2a39f6ce6d0d8a7b735c416ce diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index 9ef6e0b2dee8..bc3064155d34 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -20,12 +20,12 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; -import android.app.appsearch.AppSearchSchema; import android.app.appsearch.AppSearchSession; import android.app.appsearch.AppSearchSessionShim; import android.app.appsearch.BatchResultCallback; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByUriRequest; +import android.app.appsearch.GetSchemaResponse; import android.app.appsearch.PutDocumentsRequest; import android.app.appsearch.RemoveByUriRequest; import android.app.appsearch.ReportUsageRequest; @@ -34,6 +34,7 @@ import android.app.appsearch.SearchResultsShim; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaRequest; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; @@ -94,12 +95,20 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { @Override @NonNull - public ListenableFuture<Set<AppSearchSchema>> getSchema() { - SettableFuture<AppSearchResult<Set<AppSearchSchema>>> future = SettableFuture.create(); + public ListenableFuture<GetSchemaResponse> getSchema() { + SettableFuture<AppSearchResult<GetSchemaResponse>> future = SettableFuture.create(); mAppSearchSession.getSchema(mExecutor, future::set); return Futures.transformAsync(future, this::transformResult, mExecutor); } + @NonNull + @Override + public ListenableFuture<Set<String>> getNamespaces() { + SettableFuture<AppSearchResult<Set<String>>> future = SettableFuture.create(); + mAppSearchSession.getNamespaces(mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override @NonNull public ListenableFuture<AppSearchBatchResult<String, Void>> put( @@ -154,6 +163,14 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { return Futures.transformAsync(future, this::transformResult, mExecutor); } + @NonNull + @Override + public ListenableFuture<StorageInfo> getStorageInfo() { + SettableFuture<AppSearchResult<StorageInfo>> future = SettableFuture.create(); + mAppSearchSession.getStorageInfo(mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override public void close() { mAppSearchSession.close(); diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 69a4c18c4028..5042ce0efbd8 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -21,9 +21,11 @@ import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.GlobalSearchSession; import android.app.appsearch.GlobalSearchSessionShim; +import android.app.appsearch.ReportSystemUsageRequest; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchResultsShim; import android.app.appsearch.SearchSpec; +import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import androidx.test.core.app.ApplicationProvider; @@ -79,8 +81,24 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { return new SearchResultsShimImpl(searchResults, mExecutor); } + @NonNull + @Override + public ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request) { + SettableFuture<AppSearchResult<Void>> future = SettableFuture.create(); + mGlobalSearchSession.reportSystemUsage(request, mExecutor, future::set); + return Futures.transformAsync(future, this::transformResult, mExecutor); + } + @Override public void close() { mGlobalSearchSession.close(); } + + private <T> ListenableFuture<T> transformResult( + @NonNull AppSearchResult<T> result) throws AppSearchException { + if (!result.isSuccess()) { + throw new AppSearchException(result.getResultCode(), result.getErrorMessage()); + } + return Futures.immediateFuture(result.getResultValue()); + } } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java index 1428fb1d3c7a..206904372236 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java @@ -52,12 +52,20 @@ public interface AppSearchSessionShim extends Closeable { /** * Retrieves the schema most recently successfully provided to {@link #setSchema}. * - * @return The pending result of performing this operation. + * @return The pending {@link GetSchemaResponse} of performing this operation. */ // This call hits disk; async API prevents us from treating these calls as properties. @SuppressLint("KotlinPropertyAccess") @NonNull - ListenableFuture<Set<AppSearchSchema>> getSchema(); + ListenableFuture<GetSchemaResponse> getSchema(); + + /** + * Retrieves the set of all namespaces in the current database with at least one document. + * + * @return The pending result of performing this operation. + */ + @NonNull + ListenableFuture<Set<String>> getNamespaces(); /** * Indexes documents into the {@link AppSearchSessionShim} database. @@ -214,6 +222,17 @@ public interface AppSearchSessionShim extends Closeable { ListenableFuture<Void> remove(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); /** + * Gets the storage info for this {@link AppSearchSessionShim} database. + * + * <p>This may take time proportional to the number of documents and may be inefficient to call + * repeatedly. + * + * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object. + */ + @NonNull + ListenableFuture<StorageInfo> getStorageInfo(); + + /** * Flush all schema and document updates, additions, and deletes to disk if possible. * * @return The pending result of performing this operation. {@link diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java index 4a3c7a53d43e..fd4734cea65f 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java @@ -73,14 +73,22 @@ public class AppSearchTestUtils { public static List<GenericDocument> convertSearchResultsToDocuments( SearchResultsShim searchResults) throws Exception { - List<SearchResult> results = searchResults.getNextPage().get(); - List<GenericDocument> documents = new ArrayList<>(); - while (results.size() > 0) { - for (SearchResult result : results) { - documents.add(result.getGenericDocument()); - } - results = searchResults.getNextPage().get(); + List<SearchResult> results = retrieveAllSearchResults(searchResults); + List<GenericDocument> documents = new ArrayList<>(results.size()); + for (SearchResult result : results) { + documents.add(result.getGenericDocument()); } return documents; } + + public static List<SearchResult> retrieveAllSearchResults(SearchResultsShim searchResults) + throws Exception { + List<SearchResult> page = searchResults.getNextPage().get(); + List<SearchResult> results = new ArrayList<>(); + while (!page.isEmpty()) { + results.addAll(page); + page = searchResults.getNextPage().get(); + } + return results; + } } diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java index 440050faaa7d..f39916ee1510 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/GlobalSearchSessionShim.java @@ -18,6 +18,8 @@ package android.app.appsearch; import android.annotation.NonNull; +import com.google.common.util.concurrent.ListenableFuture; + import java.io.Closeable; /** @@ -51,6 +53,26 @@ public interface GlobalSearchSessionShim extends Closeable { @NonNull SearchResultsShim search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec); + /** + * Reports that a particular document has been used from a system surface. + * + * <p>See {@link AppSearchSessionShim#reportUsage} for a general description of document usage, + * as well as an API that can be used by the app itself. + * + * <p>Usage reported via this method is accounted separately from usage reported via {@link + * AppSearchSessionShim#reportUsage} and may be accessed using the constants {@link + * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_COUNT} and {@link + * SearchSpec#RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP}. + * + * @return The pending result of performing this operation which resolves to {@code null} on + * success. The pending result will be completed with an {@link + * android.app.appsearch.exceptions.AppSearchException} with a code of {@link + * AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an app which is not part + * of the system. + */ + @NonNull + ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request); + /** Closes the {@link GlobalSearchSessionShim}. */ @Override void close(); diff --git a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java index ad22cbaef575..f3ee2332906f 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java @@ -87,7 +87,8 @@ public class AppSearchImplPlatformTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -98,7 +99,8 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = @@ -139,7 +141,8 @@ public class AppSearchImplPlatformTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -150,7 +153,8 @@ public class AppSearchImplPlatformTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Insert package1 document GenericDocument document1 = @@ -208,7 +212,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // "schema1" is platform hidden now and package visible to package1 assertThat( @@ -234,7 +239,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // Check that "schema1" still has the same visibility settings assertThat( @@ -283,7 +289,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "schema1", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); // "schema1" is platform hidden now and package accessible assertThat( @@ -305,7 +312,8 @@ public class AppSearchImplPlatformTest { /*schemas=*/ Collections.emptyList(), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*schemaVersion=*/ 0); // Check that "schema1" is no longer considered platform hidden or package accessible assertThat( @@ -328,7 +336,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("schema1").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -351,7 +360,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -369,7 +379,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.singletonList("Schema"), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -387,7 +398,8 @@ public class AppSearchImplPlatformTest { Collections.singletonList(new AppSearchSchema.Builder("Schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() @@ -416,7 +428,8 @@ public class AppSearchImplPlatformTest { /*schemasPackageAccessible=*/ ImmutableMap.of( "Schema", ImmutableList.of(new PackageIdentifier(packageNameFoo, sha256CertFoo))), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*schemaVersion=*/ 0); assertThat( mAppSearchImpl .getVisibilityStoreLocked() diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java index e0cdeddc200e..c34c00d62f7e 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchImplTest.java @@ -26,6 +26,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResultPage; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; +import android.app.appsearch.StorageInfo; import android.app.appsearch.exceptions.AppSearchException; import android.content.Context; import android.util.ArrayMap; @@ -406,7 +407,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert enough documents. for (int i = 0; @@ -471,7 +473,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert document GenericDocument document = @@ -504,14 +507,16 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); mAppSearchImpl.setSchema( "package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert documents GenericDocument document1 = @@ -555,7 +560,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert document GenericDocument document = @@ -597,7 +603,8 @@ public class AppSearchImplTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -608,7 +615,8 @@ public class AppSearchImplTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package1 document GenericDocument document = @@ -647,7 +655,8 @@ public class AppSearchImplTest { schema1, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package2 schema List<AppSearchSchema> schema2 = @@ -658,7 +667,8 @@ public class AppSearchImplTest { schema2, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Insert package1 document GenericDocument document = @@ -735,7 +745,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -781,7 +792,8 @@ public class AppSearchImplTest { oldSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create incompatible schema List<AppSearchSchema> newSchemas = @@ -795,7 +807,8 @@ public class AppSearchImplTest { newSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text"); assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email"); } @@ -816,7 +829,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -847,7 +861,8 @@ public class AppSearchImplTest { finalSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Check the Document type has been deleted. assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document"); @@ -859,7 +874,8 @@ public class AppSearchImplTest { finalSchemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); // Check Document schema is removed. expectedProto = @@ -895,14 +911,16 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); mAppSearchImpl.setSchema( "package", "database2", schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); // Create expected schemaType proto. SchemaProto expectedProto = @@ -940,7 +958,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ true); + /*forceOverride=*/ true, + /*version=*/ 0); // Create expected schemaType list, database 1 should only contain Email but database 2 // remains in same. @@ -982,7 +1001,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -994,7 +1014,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); @@ -1006,7 +1027,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPackageToDatabases()) .containsExactlyEntriesIn(expectedMapping); } @@ -1024,7 +1046,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); // Has both databases @@ -1035,7 +1058,8 @@ public class AppSearchImplTest { Collections.singletonList(new AppSearchSchema.Builder("schema").build()), /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes); } @@ -1077,6 +1101,330 @@ public class AppSearchImplTest { } @Test + public void testReportUsage() throws Exception { + // Insert schema + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Insert two docs + GenericDocument document1 = + new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + GenericDocument document2 = + new GenericDocument.Builder<>("namespace", "uri2", "type").build(); + mAppSearchImpl.putDocument("package", "database", document1, /*logger=*/ null); + mAppSearchImpl.putDocument("package", "database", document2, /*logger=*/ null); + + // Report some usages. uri1 has 2 app and 1 system usage, uri2 has 1 app and 2 system usage. + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 10, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 20, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri1", + /*usageTimestampMillis=*/ 1000, + /*systemUsage=*/ true); + + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 100, + /*systemUsage=*/ false); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 200, + /*systemUsage=*/ true); + mAppSearchImpl.reportUsage( + "package", + "database", + "namespace", + "uri2", + /*usageTimestampMillis=*/ 150, + /*systemUsage=*/ true); + + // Sort by app usage count: uri1 should win + List<SearchResult> page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2"); + + // Sort by app usage timestamp: uri2 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec + .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1"); + + // Sort by system usage count: uri2 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri2"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri1"); + + // Sort by system usage timestamp: uri1 should win + page = + mAppSearchImpl + .query( + "package", + "database", + "", + new SearchSpec.Builder() + .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) + .setRankingStrategy( + SearchSpec + .RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP) + .build()) + .getResults(); + assertThat(page).hasSize(2); + assertThat(page.get(0).getGenericDocument().getUri()).isEqualTo("uri1"); + assertThat(page.get(1).getGenericDocument().getUri()).isEqualTo("uri2"); + } + + @Test + public void testGetStorageInfoForPackage_nonexistentPackage() throws Exception { + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("nonexistent.package"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForPackage_withoutDocument() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Since "package1" doesn't have a document, it get any space attributed to it. + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForPackage_proportionalToDocuments() throws Exception { + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + + // Insert schema for "package1" + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Insert document for "package1" + GenericDocument document = + new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database", document, /*logger=*/ null); + + // Insert schema for "package2" + mAppSearchImpl.setSchema( + "package2", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Insert two documents for "package2" + document = new GenericDocument.Builder<>("namespace", "uri1", "type").build(); + mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null); + document = new GenericDocument.Builder<>("namespace", "uri2", "type").build(); + mAppSearchImpl.putDocument("package2", "database", document, /*logger=*/ null); + + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); + long size1 = storageInfo.getSizeBytes(); + assertThat(size1).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + storageInfo = mAppSearchImpl.getStorageInfoForPackage("package2"); + long size2 = storageInfo.getSizeBytes(); + assertThat(size2).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + // Size is proportional to number of documents. Since "package2" has twice as many + // documents as "package1", its size is twice as much too. + assertThat(size2).isAtLeast(2 * size1); + } + + @Test + public void testGetStorageInfoForDatabase_nonexistentPackage() throws Exception { + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = + mAppSearchImpl.getStorageInfoForDatabase( + "nonexistent.package", "nonexistentDatabase"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_nonexistentDatabase() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // "package2" doesn't exist yet, so it shouldn't have any storage size + StorageInfo storageInfo = + mAppSearchImpl.getStorageInfoForDatabase("package1", "nonexistentDatabase"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_withoutDocument() throws Exception { + // Insert schema for "package1" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Since "package1", "database1" doesn't have a document, it get any space attributed to it. + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); + assertThat(storageInfo.getSizeBytes()).isEqualTo(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); + } + + @Test + public void testGetStorageInfoForDatabase_proportionalToDocuments() throws Exception { + // Insert schema for "package1", "database1" and "database2" + List<AppSearchSchema> schemas = + Collections.singletonList(new AppSearchSchema.Builder("type").build()); + mAppSearchImpl.setSchema( + "package1", + "database1", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + mAppSearchImpl.setSchema( + "package1", + "database2", + schemas, + /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), + /*schemasPackageAccessible=*/ Collections.emptyMap(), + /*forceOverride=*/ false, + /*version=*/ 0); + + // Add a document for "package1", "database1" + GenericDocument document = + new GenericDocument.Builder<>("namespace1", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database1", document, /*logger=*/ null); + + // Add two documents for "package1", "database2" + document = new GenericDocument.Builder<>("namespace1", "uri1", "type").build(); + mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null); + document = new GenericDocument.Builder<>("namespace1", "uri2", "type").build(); + mAppSearchImpl.putDocument("package1", "database2", document, /*logger=*/ null); + + StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); + long size1 = storageInfo.getSizeBytes(); + assertThat(size1).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database2"); + long size2 = storageInfo.getSizeBytes(); + assertThat(size2).isGreaterThan(0); + assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); + assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); + + // Size is proportional to number of documents. Since "database2" has twice as many + // documents as "database1", its size is twice as much too. + assertThat(size2).isAtLeast(2 * size1); + } + + @Test public void testThrowsExceptionIfClosed() throws Exception { Context context = ApplicationProvider.getApplicationContext(); AppSearchImpl appSearchImpl = @@ -1095,7 +1443,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); appSearchImpl.close(); @@ -1109,7 +1458,8 @@ public class AppSearchImplTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); }); expectThrows( @@ -1179,7 +1529,8 @@ public class AppSearchImplTest { "database", "namespace", "uri", - /*usageTimestampMillis=*/ 1000L); + /*usageTimestampMillis=*/ 1000L, + /*systemUsage=*/ false); }); expectThrows( @@ -1203,6 +1554,18 @@ public class AppSearchImplTest { expectThrows( IllegalStateException.class, () -> { + appSearchImpl.getStorageInfoForPackage("package"); + }); + + expectThrows( + IllegalStateException.class, + () -> { + appSearchImpl.getStorageInfoForDatabase("package", "database"); + }); + + expectThrows( + IllegalStateException.class, + () -> { appSearchImpl.persistToDisk(); }); } diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java index 467ede4efc1c..673b7ee24eff 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/AppSearchLoggerTest.java @@ -127,7 +127,8 @@ public class AppSearchLoggerTest { schemas, /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(), /*schemasPackageAccessible=*/ Collections.emptyMap(), - /*forceOverride=*/ false); + /*forceOverride=*/ false, + /*version=*/ 0); GenericDocument document = new GenericDocument.Builder<>("namespace", "uri", "type").build(); diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java index dc225f11ae80..0fe3903ec683 100644 --- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java +++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SchemaToProtoConverterTest.java @@ -32,7 +32,6 @@ public class SchemaToProtoConverterTest { public void testGetProto_Email() { AppSearchSchema emailSchema = new AppSearchSchema.Builder("Email") - .setVersion(12345) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder("subject") .setCardinality( @@ -89,7 +88,7 @@ public class SchemaToProtoConverterTest { TermMatchType.Code.PREFIX))) .build(); - assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema)) + assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(emailSchema, /*version=*/ 12345)) .isEqualTo(expectedEmailProto); assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedEmailProto)) .isEqualTo(emailSchema); @@ -142,7 +141,9 @@ public class SchemaToProtoConverterTest { PropertyConfigProto.Cardinality.Code.OPTIONAL)) .build(); - assertThat(SchemaToProtoConverter.toSchemaTypeConfigProto(musicRecordingSchema)) + assertThat( + SchemaToProtoConverter.toSchemaTypeConfigProto( + musicRecordingSchema, /*version=*/ 0)) .isEqualTo(expectedMusicRecordingProto); assertThat(SchemaToProtoConverter.toAppSearchSchema(expectedMusicRecordingProto)) .isEqualTo(musicRecordingSchema); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 1ab70e524d3c..212a9c614632 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -641,7 +641,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { public void setSchema(String packageName, String databaseName, List<Bundle> schemaBundles, List<String> schemasNotPlatformSurfaceable, Map<String, List<Bundle>> schemasPackageAccessibleBundles, boolean forceOverride, - int userId, IAppSearchResultCallback callback) throws RemoteException { + int userId, int version, IAppSearchResultCallback callback) throws RemoteException { for (Map.Entry<String, List<Bundle>> entry : schemasPackageAccessibleBundles.entrySet()) { final String key = entry.getKey(); @@ -666,6 +666,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } @Override + public void getNamespaces(String packageName, String databaseName, int userId, + IAppSearchResultCallback callback) throws RemoteException { + ignore(callback); + } + + @Override public void putDocuments(String packageName, String databaseName, List<Bundle> documentBundles, int userId, IAppSearchBatchResultCallback callback) throws RemoteException { @@ -764,7 +770,8 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { @Override public void reportUsage(String packageName, String databaseName, String namespace, - String uri, long usageTimeMillis, int userId, IAppSearchResultCallback callback) + String uri, long usageTimeMillis, boolean systemUsage, int userId, + IAppSearchResultCallback callback) throws RemoteException { ignore(callback); } |