Merge "Change session ID string to ID object" into sc-dev
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index e08d22c..a3e05dc 100644
--- a/apex/appsearch/framework/api/current.txt
+++ b/apex/appsearch/framework/api/current.txt
@@ -8,6 +8,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 31ab259..35cea3e 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 @@
/**
* 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 @@
}
/**
- * 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 @@
/**
* Asserts that this {@link AppSearchBatchResult} has no failures.
- *
* @hide
*/
public void checkSuccess() {
@@ -162,8 +161,6 @@
* 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 @@
*
* @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 @@
*
* @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 @@
*
* @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 440f633..b66837d 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.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 @@
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 @@
/** 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 @@
*
* <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 @@
/**
* Creates a new successful {@link AppSearchResult}.
- *
- * @hide
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newSuccessfulResult(
@@ -219,8 +221,6 @@
/**
* Creates a new failed {@link AppSearchResult}.
- *
- * @hide
*/
@NonNull
public static <ValueType> AppSearchResult<ValueType> newFailedResult(
@@ -228,6 +228,20 @@
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 9ea73a9..ce4aad1 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 @@
*/
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 @@
schemasPackageAccessibleBundles,
request.isForceOverride(),
mUserId,
+ request.getVersion(),
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
executor.execute(() -> {
@@ -176,7 +179,7 @@
*/
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 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 @@
request.getNamespace(),
request.getUri(),
request.getUsageTimeMillis(),
+ /*systemUsage=*/ false,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@@ -541,6 +577,25 @@
}
/**
+ * 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 fb63e16..b30ed50 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.NonNull;
import android.annotation.UserIdInt;
import android.os.RemoteException;
+import android.util.Log;
import com.android.internal.util.Preconditions;
@@ -35,14 +36,15 @@
* <p>Apps can retrieve indexed documents through the {@link #search} API.
*/
public class GlobalSearchSession implements Closeable {
-
- private final IAppSearchManager mService;
-
- @UserIdInt
- private final int mUserId;
- private boolean mIsClosed = false;
+ private static final String TAG = "AppSearchGlobalSearchSe";
private final String mPackageName;
+ @UserIdInt
+ private final int mUserId;
+ private final IAppSearchManager mService;
+
+ 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 @@
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 ba27762..d436488 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 @@
in Map<String, List<Bundle>> schemasPackageAccessibleBundles,
boolean forceOverride,
in int userId,
+ in int schemaVersion,
in IAppSearchResultCallback callback);
/**
@@ -60,8 +61,7 @@
* @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 @@
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 @@
* @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 @@
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 55f0c80..79b7b75 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 @@
*/
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 0000000..3e69367
--- /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 5ae9a41..511b42a 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 @@
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 @@
* 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.
+ * Returns {@code true} if this migrator's source type needs to be migrated to update from
+ * currentVersion to finalVersion.
*
- * <p>Providing 0 will trigger migration for any version less than the final version in the new
- * schema.
- *
- * @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 @@
* <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 @@
* <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 0000000..2e152f8
--- /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 cb20849..e66056f 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 @@
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 @@
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 @@
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 7888c8d..19d9430 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 @@
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 @@
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 @@
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 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 @@
/** 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 @@
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 @@
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 @@
}
/**
+ * 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 c1eedcd..e840ffc 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 @@
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 @@
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 @@
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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 @@
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 0000000..dc04cf3
--- /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 fae8ad4..32d7e043 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.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 @@
* @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 2806974..6e3fb82 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.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 @@
@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 @@
schemas,
schemasNotDisplayedBySystem,
schemasPackageAccessible,
- forceOverride);
+ forceOverride,
+ schemaVersion);
invokeCallbackOnResult(
callback, AppSearchResult.newSuccessfulResult(/*result=*/ null));
} catch (Throwable t) {
@@ -156,13 +159,35 @@
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 @@
@NonNull String namespace,
@NonNull String uri,
long usageTimeMillis,
+ boolean systemUsage,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
Objects.requireNonNull(databaseName);
@@ -391,9 +417,16 @@
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 ad94a0a..7847429 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.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 @@
/** 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 @@
* @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 @@
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 5e8760e..de9d609 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.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.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.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.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 @@
* @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 @@
@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 @@
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 @@
* @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 @@
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 @@
AppSearchSchema schema =
SchemaToProtoConverter.toAppSearchSchema(typeConfigBuilder);
- result.add(schema);
+ responseBuilder.addSchema(schema);
}
- return result;
+ 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 results;
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -749,6 +797,18 @@
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 @@
@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 @@
}
}
+ /** 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 @@
throwIfClosedLocked();
PersistToDiskResultProto persistToDiskResultProto =
- mIcingSearchEngineLocked.persistToDisk();
+ mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL);
checkSuccess(persistToDiskResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1275,6 +1458,153 @@
.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 ce1c9f4..800b073 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 @@
* 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 @@
@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 1d8db72..bf7e533 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 @@
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 3b5e275..d9e8adb 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 @@
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 0952215..58f430b 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 9ef6e0b..bc30641 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.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.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 @@
@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 @@
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 69a4c18..5042ce0 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.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 @@
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 1428fb1..2069043 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 @@
/**
* 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 @@
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 4a3c7a5..fd4734c 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 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 440050f..f39916e 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 @@
import android.annotation.NonNull;
+import com.google.common.util.concurrent.ListenableFuture;
+
import java.io.Closeable;
/**
@@ -51,6 +53,26 @@
@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 008b649..d54526d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1838,7 +1838,7 @@
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 @@
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 @@
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 @@
}
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 0c02c43..3926e39 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -181,3 +181,14 @@
}
+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 c1d8541..7298d87 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2328,7 +2328,7 @@
}
@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 8cbf76e..03e95fc 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -19,10 +19,12 @@
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.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 @@
@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 @@
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();
- final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
- ? mPackageInfo.getCompatibilityInfo()
- : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- final List<ResourcesLoader> loaders = mResources.getLoaders();
+ /**
+ * 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();
- return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs,
- legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */,
- compatInfo, mClassLoader, loaders);
+ 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)
+ ? packageInfo.getCompatibilityInfo()
+ : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ final List<ResourcesLoader> loaders = windowContextBase.mResources.getLoaders();
+
+ return windowContextBase.mResourcesManager.createBaseTokenResources(token, resDir,
+ splitResDirs, legacyOverlayDirs, overlayPaths, libDirs, displayId,
+ null /* overrideConfig */, compatInfo, classLoader, loaders);
}
@NonNull
@@ -3114,6 +3183,14 @@
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 ac8d3a2..74134e1 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -39,13 +39,13 @@
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.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 @@
/**
* 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 de77848..a36da88 100644
--- a/core/java/android/apphibernation/AppHibernationManager.java
+++ b/core/java/android/apphibernation/AppHibernationManager.java
@@ -33,7 +33,7 @@
*/
@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 92ff640..45a8ffd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -81,6 +81,7 @@
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 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 a42126f..e1f13f2 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -777,11 +777,11 @@
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 9df87dc..93c3cab 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -2942,7 +2942,7 @@
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 8dce852..2bed311 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -36,6 +36,7 @@
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 @@
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 d96c5c8..52e4e15 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -51,7 +51,7 @@
* 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 852ffe8..62868ac 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -127,10 +127,13 @@
* @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 @@
* 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 @@
* 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 @@
* 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
similarity index 74%
rename from core/java/android/app/WindowContext.java
rename to core/java/android/window/WindowContext.java
index d44918c..bff2252 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.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 @@
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 @@
* @hide
*/
public WindowContext(@NonNull Context base, int type, @Nullable Bundle options) {
- this(base, null /* display */, type, options);
- }
+ super(base);
- /**
- * 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 */);
-
+ 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);
- try {
- mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(),
- options);
- } 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);
+ /**
+ * 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, mType, getDisplayId(),
+ mOptions);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
@Override
@@ -135,10 +117,11 @@
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
similarity index 73%
rename from core/java/android/app/WindowTokenClient.java
rename to core/java/android/window/WindowTokenClient.java
index 82cef07..b2fe4d9 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 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 @@
* @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 @@
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 @@
@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 96df9dd..39ea8af 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -73,17 +73,17 @@
// 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
similarity index 97%
rename from core/tests/coretests/src/android/app/WindowContextTest.java
rename to core/tests/coretests/src/android/window/WindowContextTest.java
index 48b58c6..614e7c1 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.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 @@
* {@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 a5e2815..fdf0f59 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -61,6 +61,8 @@
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 @@
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 @@
}
/**
+ * 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 @@
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 @@
}
/**
+ * 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 20ff93f..8c93676 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -3234,7 +3234,60 @@
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 0826922..728f375 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.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 @@
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 0000000..67d2d405
--- /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 14cb51c..d5b5c9b 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.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 @@
*/
@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 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 6d019d2..badffce 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.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.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 5f7016e..641b38d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -235,6 +235,7 @@
"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 59a1a78..4c6e06f70 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.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.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.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 @@
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 @@
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 @@
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 @@
// (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 @@
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 109ffe3..7bc7105 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 @@
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 028cfee..9326d69 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 @@
}
/**
+ * 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 0000000..548db6b
--- /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 c44089b..f173fc7 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 @@
sendLocationSettingUpdate();
sendWifiSettingUpdate(true /* forceUpdate */);
sendAirplaneModeSettingUpdate();
+ sendMicrophoneDisableSettingUpdateForCurrentUser();
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
@@ -1123,6 +1124,7 @@
*/
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 a65adbb..8b08314 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.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 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 c99b01f..a8ca5b6 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 @@
}
/**
- * 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 5163a43..d54cf5f 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.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 @@
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 bd3f99a..60d9ea2 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -100,6 +100,21 @@
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 @@
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 @@
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 @@
// 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 ad22cba..f3ee233 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 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -98,7 +99,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package1 document
GenericDocument document1 =
@@ -139,7 +141,8 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -150,7 +153,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*schemaVersion=*/ 0);
// Insert package1 document
GenericDocument document1 =
@@ -208,7 +212,8 @@
/*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 @@
/*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 @@
/*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 @@
/*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 @@
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 @@
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 @@
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 @@
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 @@
/*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 e0cdedd..c34c00d 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.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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert enough documents.
for (int i = 0;
@@ -471,7 +473,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert document
GenericDocument document =
@@ -504,14 +507,16 @@
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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert document
GenericDocument document =
@@ -597,7 +603,8 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -608,7 +615,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package1 document
GenericDocument document =
@@ -647,7 +655,8 @@
schema1,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package2 schema
List<AppSearchSchema> schema2 =
@@ -658,7 +667,8 @@
schema2,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Insert package1 document
GenericDocument document =
@@ -735,7 +745,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -781,7 +792,8 @@
oldSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create incompatible schema
List<AppSearchSchema> newSchemas =
@@ -795,7 +807,8 @@
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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
// Create expected schemaType proto.
SchemaProto expectedProto =
@@ -847,7 +861,8 @@
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 @@
finalSchemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ true);
+ /*forceOverride=*/ true,
+ /*version=*/ 0);
// Check Document schema is removed.
expectedProto =
@@ -895,14 +911,16 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
appSearchImpl.close();
@@ -1109,7 +1458,8 @@
schemas,
/*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
/*schemasPackageAccessible=*/ Collections.emptyMap(),
- /*forceOverride=*/ false);
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
});
expectThrows(
@@ -1179,7 +1529,8 @@
"database",
"namespace",
"uri",
- /*usageTimestampMillis=*/ 1000L);
+ /*usageTimestampMillis=*/ 1000L,
+ /*systemUsage=*/ false);
});
expectThrows(
@@ -1203,6 +1554,18 @@
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 467ede4..673b7ee 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 @@
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 dc225f1..0fe3903 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 void testGetProto_Email() {
AppSearchSchema emailSchema =
new AppSearchSchema.Builder("Email")
- .setVersion(12345)
.addProperty(
new AppSearchSchema.StringPropertyConfig.Builder("subject")
.setCardinality(
@@ -89,7 +88,7 @@
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 @@
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 1ab70e5..212a9c6 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 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 @@
}
@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 @@
@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 16e0d90..ed5f1d8 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.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 @@
/**
* 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 8932892..7a0be97 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 @@
"android.view.RoundedCornersTest",
"android.view.WindowMetricsTest",
"android.view.PendingInsetsControllerTest",
- "android.app.WindowContextTest",
+ "android.window.WindowContextTest",
"android.window.WindowMetricsHelperTest",
"android.app.activity.ActivityThreadTest"
};