diff options
66 files changed, 2606 insertions, 406 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/core/api/system-current.txt b/core/api/system-current.txt index 008b6493cf1b..d54526d1c52b 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1838,7 +1838,7 @@ package android.app.usage {  package android.apphibernation { -  public final class AppHibernationManager { +  public class AppHibernationManager {      method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public java.util.List<java.lang.String> getHibernatingPackagesForUser();      method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingForUser(@NonNull String);      method @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public boolean isHibernatingGlobally(@NonNull String); @@ -4450,6 +4450,7 @@ package android.location {      method public boolean hasLowPowerMode();      method public boolean hasMeasurementCorrections();      method public boolean hasMeasurementCorrectionsExcessPathLength(); +    method public boolean hasMeasurementCorrectionsForDriving();      method public boolean hasMeasurementCorrectionsLosSats();      method @Deprecated public boolean hasMeasurementCorrectionsReflectingPane();      method public boolean hasMeasurementCorrectionsReflectingPlane(); @@ -4465,6 +4466,7 @@ package android.location {      method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);      method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrections(boolean);      method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsExcessPathLength(boolean); +    method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsForDriving(boolean);      method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsLosSats(boolean);      method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrectionsReflectingPlane(boolean);      method @NonNull public android.location.GnssCapabilities.Builder setHasMeasurementCorrelationVectors(boolean); @@ -14287,13 +14289,9 @@ package android.view.translation {    }    public final class UiTranslationManager { -    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int);      method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(@NonNull android.app.assist.ActivityId); -    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int);      method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(@NonNull android.app.assist.ActivityId); -    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int);      method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(@NonNull android.app.assist.ActivityId); -    method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int);      method @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, @NonNull android.app.assist.ActivityId);    } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index 0c02c43b1084..3926e39fd29a 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -181,3 +181,14 @@ package android.telephony.data {  } +package android.view.translation { + +  public final class UiTranslationManager { +    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void finishTranslation(int); +    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void pauseTranslation(int); +    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void resumeTranslation(int); +    method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION) public void startTranslation(@NonNull android.view.translation.TranslationSpec, @NonNull android.view.translation.TranslationSpec, @NonNull java.util.List<android.view.autofill.AutofillId>, int); +  } + +} + diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index c1d8541311a2..7298d8735eab 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2328,7 +2328,7 @@ public final class ActivityThread extends ClientTransactionHandler      }      @UnsupportedAppUsage -    final Handler getHandler() { +    public Handler getHandler() {          return mH;      } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 8cbf76e1906f..03e95fc3b6b9 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -19,10 +19,12 @@ package android.app;  import static android.content.pm.PackageManager.PERMISSION_DENIED;  import static android.content.pm.PackageManager.PERMISSION_GRANTED;  import static android.os.StrictMode.vmIncorrectContextUseEnabled; +import static android.view.WindowManager.LayoutParams.WindowType;  import android.annotation.IntDef;  import android.annotation.NonNull;  import android.annotation.Nullable; +import android.annotation.UiContext;  import android.compat.annotation.UnsupportedAppUsage;  import android.content.AutofillOptions;  import android.content.BroadcastReceiver; @@ -87,6 +89,8 @@ import android.util.Slog;  import android.view.Display;  import android.view.DisplayAdjustments;  import android.view.autofill.AutofillManager.AutofillClient; +import android.window.WindowContext; +import android.window.WindowTokenClient;  import com.android.internal.annotations.GuardedBy;  import com.android.internal.util.Preconditions; @@ -2563,23 +2567,63 @@ class ContextImpl extends Context {      @NonNull      @Override -    public WindowContext createWindowContext(int type, @NonNull Bundle options) { +    public WindowContext createWindowContext(@WindowType int type, +            @Nullable Bundle options) {          if (getDisplay() == null) { -            throw new UnsupportedOperationException("WindowContext can only be created from " -                    + "other visual contexts, such as Activity or one created with " -                    + "Context#createDisplayContext(Display)"); +            throw new UnsupportedOperationException("Please call this API with context associated" +                    + " with a display instance, such as Activity or context created via" +                    + " Context#createDisplayContext(Display), or try to invoke" +                    + " Context#createWindowContext(Display, int, Bundle)");          } -        return new WindowContext(this, type, options); +        return createWindowContextInternal(getDisplay(), type, options);      }      @NonNull      @Override -    public WindowContext createWindowContext(@NonNull Display display, int type, -            @NonNull Bundle options) { +    public WindowContext createWindowContext(@NonNull Display display, @WindowType int type, +            @Nullable Bundle options) {          if (display == null) {              throw new IllegalArgumentException("Display must not be null");          } -        return new WindowContext(this, display, type, options); +        return createWindowContextInternal(display, type, options); +    } + +    /** +     * The internal implementation of {@link Context#createWindowContext(int, Bundle)} and +     * {@link Context#createWindowContext(Display, int, Bundle)}. +     * +     * @param display The {@link Display} instance to be associated with. +     * +     * @see Context#createWindowContext(Display, int, Bundle) +     * @see Context#createWindowContext(int, Bundle) +     */ +    private WindowContext createWindowContextInternal(@NonNull Display display, +            @WindowType int type, @Nullable Bundle options) { +        // Step 1. Create a WindowTokenClient to associate with the WindowContext's Resources +        //         instance and it will be later used to receive configuration updates from the +        //         server side. +        final WindowTokenClient windowTokenClient = new WindowTokenClient(); + +        // Step 2. Create the base context of the window context, it will also create a Resources +        //         associated with the WindowTokenClient and set the token to the base context. +        final ContextImpl windowContextBase = createWindowContextBase(windowTokenClient, display); + +        // Step 3. Create a WindowContext instance and set it as the outer context of the base +        //         context to make the service obtained by #getSystemService(String) able to query +        //         the WindowContext's WindowManager instead of the default one. +        final WindowContext windowContext = new WindowContext(windowContextBase, type, options); +        windowContextBase.setOuterContext(windowContext); + +        // Step 4. Attach the WindowContext to the WindowTokenClient. In this way, when there's a +        //         configuration update from the server side, the update will then apply to +        //         WindowContext's resources. +        windowTokenClient.attachContext(windowContext); + +        // Step 5. Register the window context's token to the server side to associate with a +        //         window manager node. +        windowContext.registerWithServer(); + +        return windowContext;      }      @NonNull @@ -2588,40 +2632,65 @@ class ContextImpl extends Context {          if (display == null) {              throw new IllegalArgumentException("Display must not be null");          } -        final ContextImpl tokenContext = createBaseWindowContext(token, display); -        tokenContext.setResources(createWindowContextResources()); +        final ContextImpl tokenContext = createWindowContextBase(token, display); +        tokenContext.setResources(createWindowContextResources(tokenContext));          return tokenContext;      } - -    ContextImpl createBaseWindowContext(IBinder token, Display display) { -        ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mParams, +    /** +     * Creates the base {@link Context} for UI context to associate with a non-{@link Activity} +     * window. +     * +     * @param token The token to associate with {@link Resources} +     * @param display The {@link Display} to associate with. +     * +     * @see #createWindowContext(Display, int, Bundle) +     * @see #createTokenContext(IBinder, Display) +     */ +    @UiContext +    ContextImpl createWindowContextBase(@NonNull IBinder token, @NonNull Display display) { +        ContextImpl baseContext = new ContextImpl(this, mMainThread, mPackageInfo, mParams,                  mSplitName, token, mUser, mFlags, mClassLoader, null);          // Window contexts receive configurations directly from the server and as such do not          // need to override their display in ResourcesManager. -        context.mForceDisplayOverrideInResources = false; -        context.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; -        if (display != null) { -            context.mDisplay = display; -        } -        return context; +        baseContext.mForceDisplayOverrideInResources = false; +        baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT; +        baseContext.mDisplay = display; + +        final Resources windowContextResources = createWindowContextResources(baseContext); +        baseContext.setResources(windowContextResources); + +        return baseContext;      } -    Resources createWindowContextResources() { -        final String resDir = mPackageInfo.getResDir(); -        final String[] splitResDirs = mPackageInfo.getSplitResDirs(); -        final String[] legacyOverlayDirs = mPackageInfo.getOverlayDirs(); -        final String[] overlayPaths = mPackageInfo.getOverlayPaths(); -        final String[] libDirs = mPackageInfo.getApplicationInfo().sharedLibraryFiles; -        final int displayId = getDisplayId(); +    /** +     * Creates the {@link Resources} to associate with the {@link WindowContext}'s token. +     * +     * When there's a {@link Configuration} update, this Resources instance will be updated to match +     * the new configuration. +     * +     * @see WindowTokenClient +     * @see #getWindowContextToken() +     */ +    private static Resources createWindowContextResources(@NonNull ContextImpl windowContextBase) { +        final LoadedApk packageInfo = windowContextBase.mPackageInfo; +        final ClassLoader classLoader = windowContextBase.mClassLoader; +        final IBinder token = windowContextBase.getWindowContextToken(); + +        final String resDir = packageInfo.getResDir(); +        final String[] splitResDirs = packageInfo.getSplitResDirs(); +        final String[] legacyOverlayDirs = packageInfo.getOverlayDirs(); +        final String[] overlayPaths = packageInfo.getOverlayPaths(); +        final String[] libDirs = packageInfo.getApplicationInfo().sharedLibraryFiles; +        final int displayId = windowContextBase.getDisplayId();          final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) -                ? mPackageInfo.getCompatibilityInfo() +                ? packageInfo.getCompatibilityInfo()                  : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; -        final List<ResourcesLoader> loaders = mResources.getLoaders(); +        final List<ResourcesLoader> loaders = windowContextBase.mResources.getLoaders(); -        return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs, -                legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */, -                compatInfo, mClassLoader, loaders); +        return windowContextBase.mResourcesManager.createBaseTokenResources(token, resDir, +                splitResDirs, legacyOverlayDirs, overlayPaths, libDirs, displayId, +                null /* overrideConfig */, compatInfo, classLoader, loaders);      }      @NonNull @@ -3114,6 +3183,14 @@ class ContextImpl extends Context {          return result;      } +    @Override +    public void destroy() { +        // The final clean-up is to release BroadcastReceiver registrations. It is called in +        // ActivityThread for Activity and Service. For the context, such as WindowContext, +        // without lifecycle concept, it should be called once the context is released. +        scheduleFinalCleanup(getClass().getName(), getOuterContext().getClass().getSimpleName()); +    } +      // ----------------------------------------------------------------------      // ----------------------------------------------------------------------      // ---------------------------------------------------------------------- diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index ac8d3a261ac6..74134e16a7aa 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -39,13 +39,13 @@ import android.os.IBinder;  import android.os.Process;  import android.os.Trace;  import android.util.ArrayMap; -import android.util.ArraySet;  import android.util.DisplayMetrics;  import android.util.Log;  import android.util.Pair;  import android.util.Slog;  import android.view.Display;  import android.view.DisplayAdjustments; +import android.window.WindowContext;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.ArrayUtils; @@ -61,7 +61,6 @@ import java.util.ArrayList;  import java.util.Arrays;  import java.util.Collection;  import java.util.HashSet; -import java.util.LinkedHashSet;  import java.util.List;  import java.util.Objects;  import java.util.WeakHashMap; @@ -168,7 +167,7 @@ public class ResourcesManager {      /**       * Class containing the base configuration override and set of resources associated with an -     * Activity or {@link WindowContext}. +     * {@link Activity} or a {@link WindowContext}.       */      private static class ActivityResources {          /** diff --git a/core/java/android/apphibernation/AppHibernationManager.java b/core/java/android/apphibernation/AppHibernationManager.java index de778488df03..a36da8816d60 100644 --- a/core/java/android/apphibernation/AppHibernationManager.java +++ b/core/java/android/apphibernation/AppHibernationManager.java @@ -33,7 +33,7 @@ import java.util.List;   */  @SystemApi  @SystemService(Context.APP_HIBERNATION_SERVICE) -public final class AppHibernationManager { +public class AppHibernationManager {      private static final String TAG = "AppHibernationManager";      private final Context mContext;      private final IAppHibernationService mIAppHibernationService; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 92ff640e33b0..45a8ffd3fb5c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -81,6 +81,7 @@ import android.view.WindowManager.LayoutParams.WindowType;  import android.view.autofill.AutofillManager.AutofillClient;  import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;  import android.view.textclassifier.TextClassificationManager; +import android.window.WindowContext;  import com.android.internal.compat.IPlatformCompat;  import com.android.internal.compat.IPlatformCompatNative; @@ -6793,4 +6794,15 @@ public abstract class Context {      public boolean isUiContext() {          throw new RuntimeException("Not implemented. Must override in a subclass.");      } + +    /** +     * Called when a {@link Context} is going to be released. +     * This method can be overridden to perform the final cleanups, such as release +     * {@link BroadcastReceiver} registrations. +     * +     * @see WindowContext#destroy() +     * +     * @hide +     */ +    public void destroy() { }  } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a42126f18357..e1f13f2a93c7 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -777,11 +777,11 @@ interface IWindowManager      VerifiedDisplayHash verifyDisplayHash(in DisplayHash displayHash);      /** -     * Registers a listener for a {@link android.app.WindowContext} to handle configuration changes -     * from the server side. +     * Registers a listener for a {@link android.window.WindowContext} to handle configuration +     * changes from the server side.       * <p>       * Note that this API should be invoked after calling -     * {@link android.app.WindowTokenClient#attachContext(WindowContext)} +     * {@link android.window.WindowTokenClient#attachContext(Context)}       * </p>       *       * @param clientToken the window context's token diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 9df87dc79405..93c3cab5b787 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2942,7 +2942,7 @@ public interface WindowManager extends ViewManager {          public IBinder token = null;          /** -         * The token of {@link android.app.WindowContext}. It is usually a +         * The token of {@link android.window.WindowContext}. It is usually a           * {@link android.app.WindowTokenClient} and is used for associating the params with an           * existing node in the WindowManager hierarchy and getting the corresponding           * {@link Configuration} and {@link android.content.res.Resources} values with updates diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 8dce852a2d62..2bed3119a301 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -36,6 +36,7 @@ import android.graphics.Region;  import android.os.Bundle;  import android.os.IBinder;  import android.os.RemoteException; +import android.window.WindowContext;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.os.IResultReceiver; @@ -110,7 +111,7 @@ public final class WindowManagerImpl implements WindowManager {          return new WindowManagerImpl(displayContext, mParentWindow, mWindowContextToken);      } -    /** Creates a {@link WindowManager} for a {@link android.app.WindowContext}. */ +    /** Creates a {@link WindowManager} for a {@link WindowContext}. */      public static WindowManager createWindowContextWindowManager(Context context) {          final IBinder clientToken = context.getWindowContextToken();          return new WindowManagerImpl(context, null /* parentWindow */, clientToken); diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index d96c5c82fedb..52e4e15cc9a0 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -51,7 +51,7 @@ public final class WindowMetrics {       * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics();       * // Gets all excluding insets       * final WindowInsets windowInsets = metrics.getWindowInsets(); -     * Insets insets = windowInsets.getInsetsIgnoreVisibility(WindowInsets.Type.navigationBars() +     * Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars()       *         | WindowInsets.Type.displayCutout());       *       * int insetsWidth = insets.right + insets.left; diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java index 852ffe8303b1..62868acac756 100644 --- a/core/java/android/view/translation/UiTranslationManager.java +++ b/core/java/android/view/translation/UiTranslationManager.java @@ -127,10 +127,13 @@ public final class UiTranslationManager {       * @param destSpec {@link TranslationSpec} for the translated data.       * @param viewIds A list of the {@link View}'s {@link AutofillId} which needs to be translated       * @param taskId the Activity Task id which needs ui translation +     * @deprecated Use {@code startTranslation(TranslationSpec, TranslationSpec, List<AutofillId>, +     * ActivityId)} instead.       *       * @hide +     * @removed       */ -    // TODO, hide the APIs +    @Deprecated      @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)      @SystemApi      public void startTranslation(@NonNull TranslationSpec sourceSpec, @@ -193,10 +196,13 @@ public final class UiTranslationManager {       * NOTE: Please use {@code finishTranslation(ActivityId)} instead.       *       * @param taskId the Activity Task id which needs ui translation +     * @deprecated Use {@code finishTranslation(ActivityId)} instead.       *       * @hide +     * @removed +     *       */ -    // TODO, hide the APIs +    @Deprecated      @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)      @SystemApi      public void finishTranslation(int taskId) { @@ -240,10 +246,12 @@ public final class UiTranslationManager {       * NOTE: Please use {@code pauseTranslation(ActivityId)} instead.       *       * @param taskId the Activity Task id which needs ui translation +     * @deprecated Use {@code pauseTranslation(ActivityId)} instead.       *       * @hide +     * @removed       */ -    // TODO, hide the APIs +    @Deprecated      @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)      @SystemApi      public void pauseTranslation(int taskId) { @@ -287,10 +295,12 @@ public final class UiTranslationManager {       * NOTE: Please use {@code resumeTranslation(ActivityId)} instead.       *       * @param taskId the Activity Task id which needs ui translation +     * @deprecated Use {@code resumeTranslation(ActivityId)} instead.       *       * @hide +     * @removed       */ -    // TODO, hide the APIs +    @Deprecated      @RequiresPermission(android.Manifest.permission.MANAGE_UI_TRANSLATION)      @SystemApi      public void resumeTranslation(int taskId) { diff --git a/core/java/android/app/WindowContext.java b/core/java/android/window/WindowContext.java index d44918cf0379..bff2252e3b6d 100644 --- a/core/java/android/app/WindowContext.java +++ b/core/java/android/window/WindowContext.java @@ -13,7 +13,7 @@   * See the License for the specific language governing permissions and   * limitations under the License.   */ -package android.app; +package android.window;  import static android.view.WindowManagerImpl.createWindowContextWindowManager; @@ -28,7 +28,6 @@ import android.content.res.Configuration;  import android.os.Bundle;  import android.os.IBinder;  import android.os.RemoteException; -import android.view.Display;  import android.view.IWindowManager;  import android.view.WindowManager;  import android.view.WindowManagerGlobal; @@ -50,7 +49,9 @@ import java.lang.ref.Reference;  public class WindowContext extends ContextWrapper {      private final WindowManager mWindowManager;      private final IWindowManager mWms; -    private final WindowTokenClient mToken; +    private final @NonNull IBinder mToken; +    private final @WindowManager.LayoutParams.WindowType int mType; +    private final @Nullable Bundle mOptions;      private boolean mListenerRegistered;      private final ComponentCallbacksController mCallbacksController =              new ComponentCallbacksController(); @@ -64,47 +65,28 @@ public class WindowContext extends ContextWrapper {       * @hide       */      public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) { -        this(base, null /* display */, type, options); -    } - -    /** -     * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to -     * the token. -     * -     * @param base Base {@link Context} for this new instance. -     * @param display the {@link Display} to override. -     * @param type Window type to be used with this context. -     * @hide -     */ -    public WindowContext(@NonNull Context base, @Nullable Display display, int type, -            @Nullable Bundle options) { -        // Correct base context will be built once the token is resolved, so passing 'null' here. -        super(null /* base */); +        super(base); +        mType = type; +        mOptions = options;          mWms = WindowManagerGlobal.getWindowManagerService(); -        mToken = new WindowTokenClient(); - -        final ContextImpl contextImpl = createBaseWindowContext(base, mToken, display); -        attachBaseContext(contextImpl); -        contextImpl.setOuterContext(this); - -        mToken.attachContext(this); - +        mToken = getWindowContextToken();          mWindowManager = createWindowContextWindowManager(this); +        Reference.reachabilityFence(this); +    } + +    /** +     * Registers this {@link WindowContext} with {@link com.android.server.wm.WindowManagerService} +     * to receive configuration changes of the associated {@link WindowManager} node. +     */ +    public void registerWithServer() {          try { -            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(), -                    options); +            mListenerRegistered = mWms.registerWindowContextListener(mToken, mType, getDisplayId(), +                    mOptions);          }  catch (RemoteException e) {              throw e.rethrowFromSystemServer();          } -        Reference.reachabilityFence(this); -    } - -    private static ContextImpl createBaseWindowContext(Context outer, IBinder token, -            Display display) { -        final ContextImpl contextImpl = ContextImpl.getImpl(outer); -        return contextImpl.createBaseWindowContext(token, display);      }      @Override @@ -135,10 +117,11 @@ public class WindowContext extends ContextWrapper {          destroy();      } -    void destroy() { +    @Override +    public void destroy() {          mCallbacksController.clearCallbacks(); -        final ContextImpl impl = (ContextImpl) getBaseContext(); -        impl.scheduleFinalCleanup(getClass().getName(), "WindowContext"); +        // Called to the base ContextImpl to do final clean-up. +        getBaseContext().destroy();          Reference.reachabilityFence(this);      } diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 82cef072ad0f..b2fe4d9573f1 100644 --- a/core/java/android/app/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -13,9 +13,12 @@   * See the License for the specific language governing permissions and   * limitations under the License.   */ -package android.app; +package android.window;  import android.annotation.NonNull; +import android.app.ActivityThread; +import android.app.IWindowToken; +import android.app.ResourcesManager;  import android.content.Context;  import android.content.res.Configuration;  import android.os.Bundle; @@ -25,18 +28,22 @@ import android.view.WindowManagerGlobal;  import java.lang.ref.WeakReference;  /** - * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from - * server when window token config is updated or when it is moved between displays, and update the - * resources associated with this token on the client side. This will make sure that - * {@link WindowContext} instances will have updated resources and configuration. + * This class is used to receive {@link Configuration} changes from the associated window manager + * node on the server side, and apply the change to the {@link Context#getResources() associated + * Resources} of the attached {@link Context}. It is also used as + * {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}. + * + * @see WindowContext + * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle) + *   * @hide   */  public class WindowTokenClient extends IWindowToken.Stub {      /**       * Attached {@link Context} for this window token to update configuration and resources. -     * Initialized by {@link #attachContext(WindowContext)}. +     * Initialized by {@link #attachContext(Context)}.       */ -    private WeakReference<WindowContext> mContextRef = null; +    private WeakReference<Context> mContextRef = null;      private final ResourcesManager mResourcesManager = ResourcesManager.getInstance(); @@ -50,18 +57,16 @@ public class WindowTokenClient extends IWindowToken.Stub {       * @param context context to be attached       * @throws IllegalStateException if attached context has already existed.       */ -    void attachContext(@NonNull WindowContext context) { +    public void attachContext(@NonNull Context context) {          if (mContextRef != null) {              throw new IllegalStateException("Context is already attached.");          }          mContextRef = new WeakReference<>(context); -        final ContextImpl impl = ContextImpl.getImpl(context); -        impl.setResources(impl.createWindowContextResources());      }      @Override      public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { -        final WindowContext context = mContextRef.get(); +        final Context context = mContextRef.get();          if (context == null) {              return;          } @@ -72,8 +77,10 @@ public class WindowTokenClient extends IWindowToken.Stub {          if (displayChanged || configChanged) {              // TODO(ag/9789103): update resource manager logic to track non-activity tokens              mResourcesManager.updateResourcesForActivity(this, newConfig, newDisplayId); -            ActivityThread.currentActivityThread().getHandler().post( -                    () -> context.dispatchConfigurationChanged(newConfig)); +            if (context instanceof WindowContext) { +                ActivityThread.currentActivityThread().getHandler().post( +                        () -> ((WindowContext) context).dispatchConfigurationChanged(newConfig)); +            }          }          if (displayChanged) {              context.updateDisplay(newDisplayId); @@ -82,7 +89,7 @@ public class WindowTokenClient extends IWindowToken.Stub {      @Override      public void onWindowTokenRemoved() { -        final WindowContext context = mContextRef.get(); +        final Context context = mContextRef.get();          if (context != null) {              context.destroy();              mContextRef.clear(); diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java index 96df9dda23c7..39ea8af6fed9 100644 --- a/core/tests/coretests/src/android/view/WindowMetricsTest.java +++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java @@ -73,17 +73,17 @@ public class WindowMetricsTest {              // Check get metrics do not crash.              WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();              WindowMetrics maxMetrics = mWm.getMaximumWindowMetrics(); -            verifyMetricsSanity(currentMetrics, maxMetrics); +            verifyMetricsValidity(currentMetrics, maxMetrics);              mWm.removeViewImmediate(view);              // Check get metrics do not crash.              currentMetrics = mWm.getCurrentWindowMetrics();              maxMetrics = mWm.getMaximumWindowMetrics(); -            verifyMetricsSanity(currentMetrics, maxMetrics); +            verifyMetricsValidity(currentMetrics, maxMetrics);          }, 0);      } -    private static void verifyMetricsSanity(WindowMetrics currentMetrics, +    private static void verifyMetricsValidity(WindowMetrics currentMetrics,              WindowMetrics maxMetrics) {          Rect currentBounds = currentMetrics.getBounds();          Rect maxBounds = maxMetrics.getBounds(); diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index 48b58c6c0a1c..614e7c1d6fa4 100644 --- a/core/tests/coretests/src/android/app/WindowContextTest.java +++ b/core/tests/coretests/src/android/window/WindowContextTest.java @@ -14,7 +14,7 @@   * limitations under the License.   */ -package android.app; +package android.window;  import static android.view.Display.DEFAULT_DISPLAY;  import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; @@ -24,6 +24,9 @@ import static org.junit.Assert.assertEquals;  import static org.junit.Assert.assertFalse;  import static org.junit.Assert.assertTrue; +import android.app.Activity; +import android.app.EmptyActivity; +import android.app.Instrumentation;  import android.content.Context;  import android.content.Intent;  import android.hardware.display.DisplayManager; @@ -142,8 +145,8 @@ public class WindowContextTest {       * {@link WindowManager.LayoutParams#token}.       *       * The window context token should be overridden to -     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must -     * not be removed regardless of the release of window context. +     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be +     * removed regardless of release of window context.       */      @Test      public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease() diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java index a5e28158ab78..fdf0f59c13dc 100644 --- a/location/java/android/location/GnssCapabilities.java +++ b/location/java/android/location/GnssCapabilities.java @@ -61,6 +61,8 @@ public final class GnssCapabilities implements Parcelable {      public static final int TOP_HAL_CAPABILITY_CORRELATION_VECTOR = 4096;      /** @hide */      public static final int TOP_HAL_CAPABILITY_SATELLITE_PVT = 8192; +    /** @hide */ +    public static final int TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING = 16384;      /** @hide */      @IntDef(flag = true, prefix = {"TOP_HAL_CAPABILITY_"}, value = {TOP_HAL_CAPABILITY_SCHEDULING, @@ -69,7 +71,8 @@ public final class GnssCapabilities implements Parcelable {              TOP_HAL_CAPABILITY_MEASUREMENTS, TOP_HAL_CAPABILITY_NAV_MESSAGES,              TOP_HAL_CAPABILITY_LOW_POWER_MODE, TOP_HAL_CAPABILITY_SATELLITE_BLOCKLIST,              TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS, TOP_HAL_CAPABILITY_ANTENNA_INFO, -            TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT}) +            TOP_HAL_CAPABILITY_CORRELATION_VECTOR, TOP_HAL_CAPABILITY_SATELLITE_PVT, +            TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING})      @Retention(RetentionPolicy.SOURCE)      public @interface TopHalCapabilityFlags {} @@ -351,6 +354,17 @@ public final class GnssCapabilities implements Parcelable {      }      /** +     * Returns {@code true} if GNSS chipset will benefit from measurement corrections for driving +     * use case if provided, {@code false} otherwise. +     * +     * @hide +     */ +    @SystemApi +    public boolean hasMeasurementCorrectionsForDriving() { +        return (mTopFlags & TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING) != 0; +    } + +    /**       * Returns {@code true} if GNSS chipset supports line-of-sight satellite identification       * measurement corrections, {@code false} otherwise.       * @@ -550,6 +564,9 @@ public final class GnssCapabilities implements Parcelable {          if (hasMeasurementCorrelationVectors()) {              builder.append("MEASUREMENT_CORRELATION_VECTORS ");          } +        if (hasMeasurementCorrectionsForDriving()) { +            builder.append("MEASUREMENT_CORRECTIONS_FOR_DRIVING "); +        }          if (hasMeasurementCorrectionsLosSats()) {              builder.append("LOS_SATS ");          } @@ -748,6 +765,18 @@ public final class GnssCapabilities implements Parcelable {          }          /** +         * Sets measurement corrections for driving capability. +         * +         * @hide +         */ +        @SystemApi +        public @NonNull Builder setHasMeasurementCorrectionsForDriving(boolean capable) { +            mTopFlags = setFlag(mTopFlags, TOP_HAL_CAPABILITY_MEASUREMENT_CORRECTIONS_FOR_DRIVING, +                    capable); +            return this; +        } + +        /**           * Sets measurement corrections line-of-sight satellites capabilitity.           *           * @hide diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 20ff93f5d90a..8c9367641949 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -3234,7 +3234,60 @@ public class ConnectivityManager {          provider.setProviderId(NetworkProvider.ID_NONE);      } +    /** +     * Register or update a network offer with ConnectivityService. +     * +     * ConnectivityService keeps track of offers made by the various providers and matches +     * them to networking requests made by apps or the system. The provider supplies a score +     * and the capabilities of the network it might be able to bring up ; these act as filters +     * used by ConnectivityService to only send those requests that can be fulfilled by the +     * provider. +     * +     * The provider is under no obligation to be able to bring up the network it offers at any +     * given time. Instead, this mechanism is meant to limit requests received by providers +     * to those they actually have a chance to fulfill, as providers don't have a way to compare +     * the quality of the network satisfying a given request to their own offer. +     * +     * An offer can be updated by calling this again with the same callback object. This is +     * similar to calling unofferNetwork and offerNetwork again, but will only update the +     * provider with the changes caused by the changes in the offer. +     * +     * @param provider The provider making this offer. +     * @param score The prospective score of the network. +     * @param caps The prospective capabilities of the network. +     * @param callback The callback to call when this offer is needed or unneeded. +     * @hide +     */ +    @RequiresPermission(anyOf = { +            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, +            android.Manifest.permission.NETWORK_FACTORY}) +    public void offerNetwork(@NonNull final int providerId, +            @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, +            @NonNull final INetworkOfferCallback callback) { +        try { +            mService.offerNetwork(providerId, +                    Objects.requireNonNull(score, "null score"), +                    Objects.requireNonNull(caps, "null caps"), +                    Objects.requireNonNull(callback, "null callback")); +        } catch (RemoteException e) { +            throw e.rethrowFromSystemServer(); +        } +    } +    /** +     * Withdraw a network offer made with {@link #offerNetwork}. +     * +     * @param callback The callback passed at registration time. This must be the same object +     *                 that was passed to {@link #offerNetwork} +     * @hide +     */ +    public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { +        try { +            mService.unofferNetwork(Objects.requireNonNull(callback)); +        } catch (RemoteException e) { +            throw e.rethrowFromSystemServer(); +        } +    }      /** @hide exposed via the NetworkProvider class. */      @RequiresPermission(anyOf = {              NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 0826922e2165..728f375372b1 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -23,6 +23,7 @@ import android.net.IConnectivityDiagnosticsCallback;  import android.net.INetworkAgent;  import android.net.IOnCompleteListener;  import android.net.INetworkActivityListener; +import android.net.INetworkOfferCallback;  import android.net.IQosCallback;  import android.net.ISocketKeepaliveCallback;  import android.net.LinkProperties; @@ -221,4 +222,8 @@ interface IConnectivityManager              in IOnCompleteListener listener);      int getRestrictBackgroundStatusByCaller(); + +    void offerNetwork(int providerId, in NetworkScore score, +            in NetworkCapabilities caps, in INetworkOfferCallback callback); +    void unofferNetwork(in INetworkOfferCallback callback);  } diff --git a/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl new file mode 100644 index 000000000000..67d2d405dbed --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/INetworkOfferCallback.aidl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 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.net; + +import android.net.NetworkRequest; + +/** + * A callback registered with connectivity by network providers together with + * a NetworkOffer. + * + * When the offer is needed to satisfy some application or system component, + * connectivity will call onOfferNeeded on this callback. When this happens, + * the provider should try and bring up the network. + * + * When the offer is no longer needed, for example because the application has + * withdrawn the request or if the request is being satisfied by a network + * that this offer will never be able to beat, connectivity calls + * onOfferUnneeded. When this happens, the provider should stop trying to + * bring up the network, or tear it down if it has already been brought up. + * + * When NetworkProvider#offerNetwork is called, the provider can expect to + * immediately receive all requests that can be fulfilled by that offer and + * are not already satisfied by a better network. It is possible no such + * request is currently outstanding, because no requests have been made that + * can be satisfied by this offer, or because all such requests are already + * satisfied by a better network. + * onOfferNeeded can be called at any time after registration and until the + * offer is withdrawn with NetworkProvider#unofferNetwork is called. This + * typically happens when a new network request is filed by an application, + * or when the network satisfying a request disconnects and this offer now + * stands a chance to be the best network for it. + * + * @hide + */ +oneway interface INetworkOfferCallback { +    /** +     * Informs the registrant that the offer is needed to fulfill this request. +     * @param networkRequest the request to satisfy +     * @param providerId the ID of the provider currently satisfying +     *          this request, or NetworkProvider.ID_NONE if none. +     */ +    void onOfferNeeded(in NetworkRequest networkRequest, int providerId); + +    /** +     * Informs the registrant that the offer is no longer needed to fulfill this request. +     */ +    void onOfferUnneeded(in NetworkRequest networkRequest); +} diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java index 14cb51c85d06..d5b5c9b603ec 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java +++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java @@ -28,6 +28,11 @@ import android.os.Message;  import android.os.Messenger;  import android.util.Log; +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.concurrent.Executor; +  /**   * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device   * to networks and makes them available to the core network stack by creating @@ -78,7 +83,9 @@ public class NetworkProvider {       */      @SystemApi      public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) { -        Handler handler = new Handler(looper) { +        // TODO (b/174636568) : this class should be able to cache an instance of +        // ConnectivityManager so it doesn't have to fetch it again every time. +        final Handler handler = new Handler(looper) {              @Override              public void handleMessage(Message m) {                  switch (m.what) { @@ -159,4 +166,152 @@ public class NetworkProvider {      public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {          ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request);      } + +    /** @hide */ +    // TODO : make @SystemApi when the impl is complete +    public interface NetworkOfferCallback { +        /** Called by the system when this offer is needed to satisfy some networking request. */ +        void onOfferNeeded(@NonNull NetworkRequest request, int providerId); +        /** Called by the system when this offer is no longer needed. */ +        void onOfferUnneeded(@NonNull NetworkRequest request); +    } + +    private class NetworkOfferCallbackProxy extends INetworkOfferCallback.Stub { +        @NonNull public final NetworkOfferCallback callback; +        @NonNull private final Executor mExecutor; + +        NetworkOfferCallbackProxy(@NonNull final NetworkOfferCallback callback, +                @NonNull final Executor executor) { +            this.callback = callback; +            this.mExecutor = executor; +        } + +        @Override +        public void onOfferNeeded(final @NonNull NetworkRequest request, +                final int providerId) { +            mExecutor.execute(() -> callback.onOfferNeeded(request, providerId)); +        } + +        @Override +        public void onOfferUnneeded(final @NonNull NetworkRequest request) { +            mExecutor.execute(() -> callback.onOfferUnneeded(request)); +        } +    } + +    @GuardedBy("mProxies") +    @NonNull private final ArrayList<NetworkOfferCallbackProxy> mProxies = new ArrayList<>(); + +    // Returns the proxy associated with this callback, or null if none. +    @Nullable +    private NetworkOfferCallbackProxy findProxyForCallback(@NonNull final NetworkOfferCallback cb) { +        synchronized (mProxies) { +            for (final NetworkOfferCallbackProxy p : mProxies) { +                if (p.callback == cb) return p; +            } +        } +        return null; +    } + +    /** +     * Register or update an offer for network with the passed caps and score. +     * +     * A NetworkProvider's job is to provide networks. This function is how a provider tells the +     * connectivity stack what kind of network it may provide. The score and caps arguments act +     * as filters that the connectivity stack uses to tell when the offer is necessary. When an +     * offer might be advantageous over existing networks, the provider will receive a call to +     * the associated callback's {@link NetworkOfferCallback#onOfferNeeded} method. The provider +     * should then try to bring up this network. When an offer is no longer needed, the stack +     * will inform the provider by calling {@link NetworkOfferCallback#onOfferUnneeded}. The +     * provider should stop trying to bring up such a network, or disconnect it if it already has +     * one. +     * +     * The stack determines what offers are needed according to what networks are currently +     * available to the system, and what networking requests are made by applications. If an +     * offer looks like it could be a better choice than any existing network for any particular +     * request, that's when the stack decides the offer is needed. If the current networking +     * requests are all satisfied by networks that this offer can't possibly be a better match +     * for, that's when the offer is unneeded. An offer starts off as unneeded ; the provider +     * should not try to bring up the network until {@link NetworkOfferCallback#onOfferNeeded} +     * is called. +     * +     * Note that the offers are non-binding to the providers, in particular because providers +     * often don't know if they will be able to bring up such a network at any given time. For +     * example, no wireless network may be in range when the offer is needed. This is fine and +     * expected ; the provider should simply continue to try to bring up the network and do so +     * if/when it becomes possible. In the mean time, the stack will continue to satisfy requests +     * with the best network currently available, or if none, keep the apps informed that no +     * network can currently satisfy this request. When/if the provider can bring up the network, +     * the connectivity stack will match it against requests, and inform interested apps of the +     * availability of this network. This may, in turn, render the offer of some other provider +     * unneeded if all requests it used to satisfy are now better served by this network. +     * +     * A network can become unneeded for a reason like the above : whether the provider managed +     * to bring up the offered network after it became needed or not, some other provider may +     * bring up a better network than this one, making this offer unneeded. A network may also +     * become unneeded if the application making the request withdrew it (for example, after it +     * is done transferring data, or if the user canceled an operation). +     * +     * The capabilities and score act as filters as to what requests the provider will see. +     * They are not promises, but for best performance, the providers should strive to put +     * as much known information as possible in the offer. For capabilities in particular, it +     * should put all NetworkAgent-managed capabilities a network may have, even if it doesn't +     * have them at first. This applies to INTERNET, for example ; if a provider thinks the +     * network it can bring up for this offer may offer Internet access it should include the +     * INTERNET bit. It's fine if the brought up network ends up not actually having INTERNET. +     * +     * TODO : in the future, to avoid possible infinite loops, there should be constraints on +     * what can be put in capabilities of networks brought up for an offer. If a provider might +     * bring up a network with or without INTERNET, then it should file two offers : this will +     * let it know precisely what networks are needed, so it can avoid bringing up networks that +     * won't actually satisfy requests and remove the risk for bring-up-bring-down loops. +     * +     * @hide +     */ +    // TODO : make @SystemApi when the impl is complete +    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) +    public void offerNetwork(@NonNull final NetworkScore score, +            @NonNull final NetworkCapabilities caps, @NonNull final Executor executor, +            @NonNull final NetworkOfferCallback callback) { +        // Can't offer a network with a provider that is not yet registered or already unregistered. +        final int providerId = mProviderId; +        if (providerId == ID_NONE) return; +        NetworkOfferCallbackProxy proxy = null; +        synchronized (mProxies) { +            for (final NetworkOfferCallbackProxy existingProxy : mProxies) { +                if (existingProxy.callback == callback) { +                    proxy = existingProxy; +                    break; +                } +            } +            if (null == proxy) { +                proxy = new NetworkOfferCallbackProxy(callback, executor); +                mProxies.add(proxy); +            } +        } +        mContext.getSystemService(ConnectivityManager.class) +                .offerNetwork(providerId, score, caps, proxy); +    } + +    /** +     * Withdraw a network offer previously made to the networking stack. +     * +     * If a provider can no longer provide a network they offered, it should call this method. +     * An example of usage could be if the hardware necessary to bring up the network was turned +     * off in UI by the user. Note that because offers are never binding, the provider might +     * alternatively decide not to withdraw this offer and simply refuse to bring up the network +     * even when it's needed. However, withdrawing the request is slightly more resource-efficient +     * because the networking stack won't have to compare this offer to exiting networks to see +     * if it could beat any of them, and may be advantageous to the provider's implementation that +     * can rely on no longer receiving callbacks for a network that they can't bring up anyways. +     * +     * @hide +     */ +    // TODO : make @SystemApi when the impl is complete +    @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) +    public void unofferNetwork(final @NonNull NetworkOfferCallback callback) { +        final NetworkOfferCallbackProxy proxy = findProxyForCallback(callback); +        if (null == proxy) return; +        mProxies.remove(proxy); +        mContext.getSystemService(ConnectivityManager.class).unofferNetwork(proxy); +    }  } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 6d019d2aa6f5..badffce3259a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -38,7 +38,6 @@ import android.app.ActivityOptions;  import android.app.ExitTransitionCoordinator;  import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks;  import android.app.Notification; -import android.app.WindowContext;  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; @@ -74,6 +73,7 @@ import android.view.WindowManager;  import android.view.accessibility.AccessibilityEvent;  import android.view.accessibility.AccessibilityManager;  import android.widget.Toast; +import android.window.WindowContext;  import com.android.internal.app.ChooserActivity;  import com.android.internal.logging.UiEventLogger; diff --git a/services/core/Android.bp b/services/core/Android.bp index 5f7016e724ef..641b38db0c07 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -235,6 +235,7 @@ filegroup {          "java/com/android/server/connectivity/NetworkAgentInfo.java",          "java/com/android/server/connectivity/NetworkDiagnostics.java",          "java/com/android/server/connectivity/NetworkNotificationManager.java", +        "java/com/android/server/connectivity/NetworkOffer.java",          "java/com/android/server/connectivity/NetworkRanker.java",          "java/com/android/server/connectivity/OsCompat.java",          "java/com/android/server/connectivity/PermissionMonitor.java", diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 59a1a7817cfd..4c6e06f70277 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -15,7 +15,6 @@   */  package com.android.server; -  import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;  import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;  import static android.content.pm.PackageManager.FEATURE_WATCH; @@ -122,6 +121,7 @@ import android.net.INetworkActivityListener;  import android.net.INetworkAgent;  import android.net.INetworkMonitor;  import android.net.INetworkMonitorCallbacks; +import android.net.INetworkOfferCallback;  import android.net.IOnCompleteListener;  import android.net.IQosCallback;  import android.net.ISocketKeepaliveCallback; @@ -229,6 +229,7 @@ import com.android.net.module.util.PermissionUtils;  import com.android.server.connectivity.AutodestructReference;  import com.android.server.connectivity.DnsManager;  import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate; +import com.android.server.connectivity.FullScore;  import com.android.server.connectivity.KeepaliveTracker;  import com.android.server.connectivity.LingerMonitor;  import com.android.server.connectivity.MockableSystemProperties; @@ -236,6 +237,7 @@ import com.android.server.connectivity.NetworkAgentInfo;  import com.android.server.connectivity.NetworkDiagnostics;  import com.android.server.connectivity.NetworkNotificationManager;  import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import com.android.server.connectivity.NetworkOffer;  import com.android.server.connectivity.NetworkRanker;  import com.android.server.connectivity.PermissionMonitor;  import com.android.server.connectivity.ProfileNetworkPreferences; @@ -593,6 +595,18 @@ public class ConnectivityService extends IConnectivityManager.Stub      private static final int EVENT_UID_BLOCKED_REASON_CHANGED = 51;      /** +     * Event to register a new network offer +     * obj = NetworkOffer +     */ +    private static final int EVENT_REGISTER_NETWORK_OFFER = 52; + +    /** +     * Event to unregister an existing network offer +     * obj = INetworkOfferCallback +     */ +    private static final int EVENT_UNREGISTER_NETWORK_OFFER = 53; + +    /**       * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification       * should be shown.       */ @@ -4565,6 +4579,18 @@ public class ConnectivityService extends IConnectivityManager.Stub                      handleUnregisterNetworkProvider((Messenger) msg.obj);                      break;                  } +                case EVENT_REGISTER_NETWORK_OFFER: { +                    handleRegisterNetworkOffer((NetworkOffer) msg.obj); +                    break; +                } +                case EVENT_UNREGISTER_NETWORK_OFFER: { +                    final NetworkOfferInfo offer = +                            findNetworkOfferInfoByCallback((INetworkOfferCallback) msg.obj); +                    if (null != offer) { +                        handleUnregisterNetworkOffer(offer); +                    } +                    break; +                }                  case EVENT_REGISTER_NETWORK_AGENT: {                      final Pair<NetworkAgentInfo, INetworkMonitor> arg =                              (Pair<NetworkAgentInfo, INetworkMonitor>) msg.obj; @@ -6046,12 +6072,37 @@ public class ConnectivityService extends IConnectivityManager.Stub          mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));      } +    @Override +    public void offerNetwork(final int providerId, +            @NonNull final NetworkScore score, @NonNull final NetworkCapabilities caps, +            @NonNull final INetworkOfferCallback callback) { +        final NetworkOffer offer = new NetworkOffer( +                FullScore.makeProspectiveScore(score, caps), caps, callback, providerId); +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_OFFER, offer)); +    } + +    @Override +    public void unofferNetwork(@NonNull final INetworkOfferCallback callback) { +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_OFFER, callback)); +    } +      private void handleUnregisterNetworkProvider(Messenger messenger) {          NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);          if (npi == null) {              loge("Failed to find Messenger in unregisterNetworkProvider");              return;          } +        // Unregister all the offers from this provider +        final ArrayList<NetworkOfferInfo> toRemove = new ArrayList<>(); +        for (final NetworkOfferInfo noi : mNetworkOffers) { +            if (noi.offer.providerId == npi.providerId) { +                // Can't call handleUnregisterNetworkOffer here because iteration is in progress +                toRemove.add(noi); +            } +        } +        for (NetworkOfferInfo noi : toRemove) { +            handleUnregisterNetworkOffer(noi); +        }          if (DBG) log("unregisterNetworkProvider for " + npi.name);      } @@ -6090,6 +6141,10 @@ public class ConnectivityService extends IConnectivityManager.Stub      // (on the handler thread).      private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>(); +    // Must only be accessed on the handler thread +    @NonNull +    private final ArrayList<NetworkOfferInfo> mNetworkOffers = new ArrayList<>(); +      @GuardedBy("mBlockedAppUids")      private final HashSet<Integer> mBlockedAppUids = new HashSet<>(); @@ -6399,6 +6454,58 @@ public class ConnectivityService extends IConnectivityManager.Stub          updateUids(nai, null, nai.networkCapabilities);      } +    private class NetworkOfferInfo implements IBinder.DeathRecipient { +        @NonNull public final NetworkOffer offer; + +        NetworkOfferInfo(@NonNull final NetworkOffer offer) { +            this.offer = offer; +        } + +        @Override +        public void binderDied() { +            mHandler.post(() -> handleUnregisterNetworkOffer(this)); +        } +    } + +    /** +     * Register or update a network offer. +     * @param newOffer The new offer. If the callback member is the same as an existing +     *                 offer, it is an update of that offer. +     */ +    private void handleRegisterNetworkOffer(@NonNull final NetworkOffer newOffer) { +        ensureRunningOnConnectivityServiceThread(); + +        final NetworkOfferInfo existingOffer = findNetworkOfferInfoByCallback(newOffer.callback); +        if (null != existingOffer) { +            handleUnregisterNetworkOffer(existingOffer); +            newOffer.migrateFrom(existingOffer.offer); +        } +        final NetworkOfferInfo noi = new NetworkOfferInfo(newOffer); +        try { +            noi.offer.callback.asBinder().linkToDeath(noi, 0 /* flags */); +        } catch (RemoteException e) { +            noi.binderDied(); +            return; +        } +        mNetworkOffers.add(noi); +        // TODO : send requests to the provider. +    } + +    private void handleUnregisterNetworkOffer(@NonNull final NetworkOfferInfo noi) { +        ensureRunningOnConnectivityServiceThread(); +        mNetworkOffers.remove(noi); +        noi.offer.callback.asBinder().unlinkToDeath(noi, 0 /* flags */); +    } + +    @Nullable private NetworkOfferInfo findNetworkOfferInfoByCallback( +            @NonNull final INetworkOfferCallback callback) { +        ensureRunningOnConnectivityServiceThread(); +        for (final NetworkOfferInfo noi : mNetworkOffers) { +            if (noi.offer.callback.equals(callback)) return noi; +        } +        return null; +    } +      /**       * Called when receiving LinkProperties directly from a NetworkAgent.       * Stores into |nai| any data coming from the agent that might also be written to the network's diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 109ffe38f332..7bc71051c627 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -1922,6 +1922,7 @@ public class AppOpsService extends IAppOpsService.Stub {          synchronized (this) {              if (mWriteScheduled) {                  mWriteScheduled = false; +                mFastWriteScheduled = false;                  mHandler.removeCallbacks(mWriteRunner);                  doWrite = true;              } diff --git a/services/core/java/com/android/server/connectivity/FullScore.java b/services/core/java/com/android/server/connectivity/FullScore.java index 028cfee36593..9326d692f6e4 100644 --- a/services/core/java/com/android/server/connectivity/FullScore.java +++ b/services/core/java/com/android/server/connectivity/FullScore.java @@ -16,6 +16,7 @@  package com.android.server.connectivity; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;  import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;  import static android.net.NetworkCapabilities.TRANSPORT_VPN; @@ -116,6 +117,33 @@ public class FullScore {      }      /** +     * Given a score supplied by the NetworkAgent, produce a prospective score for an offer. +     * +     * NetworkOffers have score filters that are compared to the scores of actual networks +     * to see if they could possibly beat the current satisfier. Some things the agent can't +     * know in advance ; a good example is the validation bit – some networks will validate, +     * others won't. For comparison purposes, assume the best, so all possibly beneficial +     * networks will be brought up. +     * +     * @param score the score supplied by the agent for this offer +     * @param caps the capabilities supplied by the agent for this offer +     * @return a FullScore appropriate for comparing to actual network's scores. +     */ +    public static FullScore makeProspectiveScore(@NonNull final NetworkScore score, +            @NonNull final NetworkCapabilities caps) { +        // If the network offers Internet access, it may validate. +        final boolean mayValidate = caps.hasCapability(NET_CAPABILITY_INTERNET); +        // VPN transports are known in advance. +        final boolean vpn = caps.hasTransport(TRANSPORT_VPN); +        // The network hasn't been chosen by the user (yet, at least). +        final boolean everUserSelected = false; +        // Don't assume the user will accept unvalidated connectivity. +        final boolean acceptUnvalidated = false; +        return withPolicies(score.getLegacyInt(), mayValidate, vpn, everUserSelected, +                acceptUnvalidated); +    } + +    /**       * Return a new score given updated caps and config.       *       * @param caps the NetworkCapabilities of the network diff --git a/services/core/java/com/android/server/connectivity/NetworkOffer.java b/services/core/java/com/android/server/connectivity/NetworkOffer.java new file mode 100644 index 000000000000..548db6b6ddbb --- /dev/null +++ b/services/core/java/com/android/server/connectivity/NetworkOffer.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 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 com.android.server.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.INetworkOfferCallback; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; + +import java.util.Objects; + + +/** + * Represents an offer made by a NetworkProvider to create a network if a need arises. + * + * This class contains the prospective score and capabilities of the network. The provider + * is not obligated to caps able to create a network satisfying this, nor to build a network + * with the exact score and/or capabilities passed ; after all, not all providers know in + * advance what a network will look like after it's connected. Instead, this is meant as a + * filter to limit requests sent to the provider by connectivity to those that this offer stands + * a chance to fulfill. + * + * @see NetworkProvider#offerNetwork. + * + * @hide + */ +public class NetworkOffer { +    @NonNull public final FullScore score; +    @NonNull public final NetworkCapabilities caps; +    @NonNull public final INetworkOfferCallback callback; +    @NonNull public final int providerId; + +    private static NetworkCapabilities emptyCaps() { +        final NetworkCapabilities nc = new NetworkCapabilities(); +        return nc; +    } + +    // Ideally the caps argument would be non-null, but null has historically meant no filter +    // and telephony passes null. Keep backward compatibility. +    public NetworkOffer(@NonNull final FullScore score, +            @Nullable final NetworkCapabilities caps, +            @NonNull final INetworkOfferCallback callback, +            @NonNull final int providerId) { +        this.score = Objects.requireNonNull(score); +        this.caps = null != caps ? caps : emptyCaps(); +        this.callback = Objects.requireNonNull(callback); +        this.providerId = providerId; +    } + +    /** +     * Migrate from, and take over, a previous offer. +     * +     * When an updated offer is sent from a provider, call this method on the new offer, passing +     * the old one, to take over the state. +     * +     * @param previousOffer +     */ +    public void migrateFrom(@NonNull final NetworkOffer previousOffer) { +        if (!callback.equals(previousOffer.callback)) { +            throw new IllegalArgumentException("Can only migrate from a previous version of" +                    + " the same offer"); +        } +    } + +    /** +     * Returns whether an offer can satisfy a NetworkRequest, according to its capabilities. +     * @param request The request to test against. +     * @return Whether this offer can satisfy the request. +     */ +    public final boolean canSatisfy(@NonNull final NetworkRequest request) { +        return request.networkCapabilities.satisfiedByNetworkCapabilities(caps); +    } + +    @Override +    public String toString() { +        return "NetworkOffer [ Score " + score + " ]"; +    } +} diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index c44089b7817f..f173fc7386ba 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -691,6 +691,7 @@ public class ContextHubService extends IContextHubService.Stub {              sendLocationSettingUpdate();              sendWifiSettingUpdate(true /* forceUpdate */);              sendAirplaneModeSettingUpdate(); +            sendMicrophoneDisableSettingUpdateForCurrentUser();              mTransactionManager.onHubReset();              queryNanoAppsInternal(contextHubId); @@ -1123,6 +1124,7 @@ public class ContextHubService extends IContextHubService.Stub {       */      public void onUserChanged() {          Log.d(TAG, "User changed to id: " + getCurrentUserId()); +        sendLocationSettingUpdate();          sendMicrophoneDisableSettingUpdateForCurrentUser();      }  } diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index a65adbbf5044..8b08314136d4 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -33,6 +33,7 @@ import android.os.RemoteException;  import android.util.ArrayMap;  import android.view.View;  import android.view.WindowManager.LayoutParams.WindowType; +import android.window.WindowContext;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.protolog.common.ProtoLog; @@ -40,22 +41,21 @@ import com.android.internal.protolog.common.ProtoLog;  import java.util.Objects;  /** - * A controller to register/unregister {@link WindowContainerListener} for - * {@link android.app.WindowContext}. + * A controller to register/unregister {@link WindowContainerListener} for {@link WindowContext}.   *   * <ul> - *   <li>When a {@link android.app.WindowContext} is created, it registers the listener via + *   <li>When a {@link WindowContext} is created, it registers the listener via   *     {@link WindowManagerService#registerWindowContextListener(IBinder, int, int, Bundle)}   *     automatically.</li> - *   <li>When the {@link android.app.WindowContext} adds the first window to the screen via + *   <li>When the {@link WindowContext} adds the first window to the screen via   *     {@link android.view.WindowManager#addView(View, android.view.ViewGroup.LayoutParams)},   *     {@link WindowManagerService} then updates the {@link WindowContextListenerImpl} to listen   *     to corresponding {@link WindowToken} via this controller.</li> - *   <li>When the {@link android.app.WindowContext} is GCed, it unregisters the previously + *   <li>When the {@link WindowContext} is GCed, it unregisters the previously   *     registered listener via   *     {@link WindowManagerService#unregisterWindowContextListener(IBinder)}.   *     {@link WindowManagerService} is also responsible for removing the - *     {@link android.app.WindowContext} created {@link WindowToken}.</li> + *     {@link WindowContext} created {@link WindowToken}.</li>   * </ul>   * <p>Note that the listener may be removed earlier than the   * {@link #unregisterWindowContainerListener(IBinder)} if the listened {@link WindowContainer} was diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c99b01f1d2b5..a8ca5b6d6f37 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2708,7 +2708,7 @@ public class WindowManagerService extends IWindowManager.Stub      }      /** -     * Registers a listener for a {@link android.app.WindowContext} to subscribe to configuration +     * Registers a listener for a {@link android.window.WindowContext} to subscribe to configuration       * changes of a {@link DisplayArea}.       *       * @param clientToken the window context's token diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 5163a431bb84..d54cf5f17b4a 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -54,6 +54,7 @@ import android.view.DisplayInfo;  import android.view.InsetsState;  import android.view.SurfaceControl;  import android.view.WindowManager; +import android.window.WindowContext;  import com.android.internal.protolog.common.ProtoLog;  import com.android.server.policy.WindowManagerPolicy; @@ -109,7 +110,7 @@ class WindowToken extends WindowContainer<WindowState> {      private FixedRotationTransformState mFixedRotationTransformState;      /** -     * When set to {@code true}, this window token is created from {@link android.app.WindowContext} +     * When set to {@code true}, this window token is created from {@link WindowContext}       */      private final boolean mFromClientToken; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index bd3f99a5efb8..60d9ea20d06a 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -100,6 +100,21 @@ static bool isPageAligned(IncFsSize s) {      return (s & (Constants::blockSize - 1)) == 0;  } +static bool getEnforceReadLogsMaxIntervalForSystemDataLoaders() { +    return android::base::GetBoolProperty("debug.incremental.enforce_readlogs_max_interval_for_" +                                          "system_dataloaders", +                                          false); +} + +static Seconds getReadLogsMaxInterval() { +    constexpr int limit = duration_cast<Seconds>(Constants::readLogsMaxInterval).count(); +    int readlogs_max_interval_secs = +            std::min(limit, +                     android::base::GetIntProperty< +                             int>("debug.incremental.readlogs_max_interval_sec", limit)); +    return Seconds{readlogs_max_interval_secs}; +} +  template <base::LogSeverity level = base::ERROR>  bool mkdirOrLog(std::string_view name, int mode = 0770, bool allowExisting = true) {      auto cstr = path::c_str(name); @@ -711,7 +726,8 @@ bool IncrementalService::startLoading(StorageId storageId,          dataLoaderStub = ifs->dataLoaderStub;      } -    if (dataLoaderStub->isSystemDataLoader()) { +    if (dataLoaderStub->isSystemDataLoader() && +        !getEnforceReadLogsMaxIntervalForSystemDataLoaders()) {          // Readlogs from system dataloader (adb) can always be collected.          ifs->startLoadingTs = TimePoint::max();      } else { @@ -719,7 +735,7 @@ bool IncrementalService::startLoading(StorageId storageId,          const auto startLoadingTs = mClock->now();          ifs->startLoadingTs = startLoadingTs;          // Setup a callback to disable the readlogs after max interval. -        addTimedJob(*mTimedQueue, storageId, Constants::readLogsMaxInterval, +        addTimedJob(*mTimedQueue, storageId, getReadLogsMaxInterval(),                      [this, storageId, startLoadingTs]() {                          const auto ifs = getIfs(storageId);                          if (!ifs) { @@ -807,7 +823,7 @@ int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLog      // Check installation time.      const auto now = mClock->now();      const auto startLoadingTs = ifs->startLoadingTs; -    if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) { +    if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) {          LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "                     << storageId;          return -EPERM; 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);          } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java index 16e0d90ac2d3..ed5f1d8c9fc5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java @@ -38,6 +38,7 @@ import android.content.res.Configuration;  import android.os.Bundle;  import android.os.IBinder;  import android.platform.test.annotations.Presubmit; +import android.window.WindowContext;  import androidx.test.filters.SmallTest; @@ -208,7 +209,7 @@ public class WindowTokenTests extends WindowTestsBase {      /**       * Test that {@link android.view.SurfaceControl} should not be created for the -     * {@link WindowToken} which was created for {@link android.app.WindowContext} initially, the +     * {@link WindowToken} which was created for {@link WindowContext} initially, the       * surface should be create after addWindow for this token.       */      @Test diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java index 8932892c3aec..7a0be97fa2bf 100644 --- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java +++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java @@ -59,7 +59,7 @@ public final class FrameworksTestsFilter extends SelectTest {              "android.view.RoundedCornersTest",              "android.view.WindowMetricsTest",              "android.view.PendingInsetsControllerTest", -            "android.app.WindowContextTest", +            "android.window.WindowContextTest",              "android.window.WindowMetricsHelperTest",              "android.app.activity.ActivityThreadTest"      };  |