Merge "Introduce new colorful wallpaperTextColor" into sc-dev
diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt
index e08d22c..47cf17c 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,13 +141,16 @@
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>>);
method public void reportUsage(@NonNull android.app.appsearch.ReportUsageRequest, @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);
- method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
+ method @Deprecated public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
+ method public void setSchema(@NonNull android.app.appsearch.SetSchemaRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appsearch.AppSearchResult<android.app.appsearch.SetSchemaResponse>>);
}
public interface BatchResultCallback<KeyType, ValueType> {
@@ -203,16 +217,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 +273,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 +309,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 +317,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 +359,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 +373,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 +395,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 +407,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 +418,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 +461,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..bf733ed 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;
@@ -102,6 +104,18 @@
}
/**
+ * TODO(b/181887768): This method exists only for dogfooder transition and must be removed.
+ * @deprecated This method exists only for dogfooder transition and must be removed.
+ */
+ @Deprecated
+ public void setSchema(
+ @NonNull SetSchemaRequest request,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
+ @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
+ setSchema(request, callbackExecutor, callbackExecutor, callback);
+ }
+
+ /**
* Sets the schema that represents the organizational structure of data within the AppSearch
* database.
*
@@ -111,7 +125,9 @@
* no-op call.
*
* @param request the schema to set or update the AppSearch database to.
- * @param executor Executor on which to invoke the callback.
+ * @param workExecutor Executor on which to schedule heavy client-side background work such as
+ * transforming documents.
+ * @param callbackExecutor Executor on which to invoke the callback.
* @param callback Callback to receive errors resulting from setting the schema. If the
* operation succeeds, the callback will be invoked with {@code null}.
*/
@@ -119,10 +135,12 @@
// exposed.
public void setSchema(
@NonNull SetSchemaRequest request,
- @NonNull @CallbackExecutor Executor executor,
+ @NonNull Executor workExecutor,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
Objects.requireNonNull(request);
- Objects.requireNonNull(executor);
+ Objects.requireNonNull(workExecutor);
+ Objects.requireNonNull(callbackExecutor);
Objects.requireNonNull(callback);
Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
List<Bundle> schemaBundles = new ArrayList<>(request.getSchemas().size());
@@ -148,12 +166,15 @@
schemasPackageAccessibleBundles,
request.isForceOverride(),
mUserId,
+ request.getVersion(),
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
- executor.execute(() -> {
+ callbackExecutor.execute(() -> {
if (result.isSuccess()) {
callback.accept(
// TODO(b/177266929) implement Migration in platform.
+ // TODO(b/183177268): once migration is implemented, run
+ // it on workExecutor.
AppSearchResult.newSuccessfulResult(
new SetSchemaResponse.Builder().build()));
} else {
@@ -176,7 +197,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 +210,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);
}
@@ -297,8 +350,7 @@
// Translate successful results
for (Map.Entry<String, Bundle> bundleEntry :
- (Set<Map.Entry<String, Bundle>>)
- result.getSuccesses().entrySet()) {
+ ((Map<String, Bundle>) result.getSuccesses()).entrySet()) {
GenericDocument document;
try {
document = new GenericDocument(bundleEntry.getValue());
@@ -317,8 +369,8 @@
// Translate failed results
for (Map.Entry<String, AppSearchResult<Bundle>> bundleEntry :
- (Set<Map.Entry<String, AppSearchResult<Bundle>>>)
- result.getFailures().entrySet()) {
+ ((Map<String, AppSearchResult<Bundle>>)
+ result.getFailures()).entrySet()) {
documentResultBuilder.setFailure(
bundleEntry.getKey(),
bundleEntry.getValue().getResultCode(),
@@ -437,6 +489,7 @@
request.getNamespace(),
request.getUri(),
request.getUsageTimeMillis(),
+ /*systemUsage=*/ false,
mUserId,
new IAppSearchResultCallback.Stub() {
public void onResult(AppSearchResult result) {
@@ -541,6 +594,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..f0de496 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;
@@ -88,18 +89,26 @@
@NonNull
public ListenableFuture<SetSchemaResponse> setSchema(@NonNull SetSchemaRequest request) {
SettableFuture<AppSearchResult<SetSchemaResponse>> future = SettableFuture.create();
- mAppSearchSession.setSchema(request, mExecutor, future::set);
+ mAppSearchSession.setSchema(request, mExecutor, mExecutor, future::set);
return Futures.transformAsync(future, this::transformResult, mExecutor);
}
@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/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index 80698f7..bebf019 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -69,10 +69,12 @@
method public boolean advance(@NonNull android.media.MediaParser.SeekableInputReader) throws java.io.IOException;
method @NonNull public static android.media.MediaParser create(@NonNull android.media.MediaParser.OutputConsumer, @NonNull java.lang.String...);
method @NonNull public static android.media.MediaParser createByName(@NonNull String, @NonNull android.media.MediaParser.OutputConsumer);
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method @NonNull public String getParserName();
method @NonNull public static java.util.List<java.lang.String> getParserNames(@NonNull android.media.MediaFormat);
method public void release();
method public void seek(@NonNull android.media.MediaParser.SeekPoint);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method @NonNull public android.media.MediaParser setParameter(@NonNull String, @NonNull Object);
method public boolean supportsParameter(@NonNull String);
field public static final String PARAMETER_ADTS_ENABLE_CBR_SEEKING = "android.media.mediaparser.adts.enableCbrSeeking";
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index 8bdca76..cff422d 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -21,6 +21,7 @@
import android.annotation.Nullable;
import android.annotation.StringDef;
import android.media.MediaCodec.CryptoInfo;
+import android.media.metrics.LogSessionId;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
@@ -74,6 +75,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
@@ -1066,6 +1068,7 @@
private boolean mReleased;
// MediaMetrics fields.
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
private final boolean mCreatedByName;
private final SparseArray<Format> mTrackFormats;
private String mLastObservedExceptionName;
@@ -1328,6 +1331,7 @@
MEDIAMETRICS_PARAMETER_LIST_MAX_LENGTH));
nativeSubmitMetrics(
+ // TODO: mLogSessionId,
mParserName,
mCreatedByName,
String.join(MEDIAMETRICS_ELEMENT_SEPARATOR, mParserNamesPool),
@@ -1341,6 +1345,15 @@
videoHeight);
}
+ public void setLogSessionId(@NonNull LogSessionId sessionId) {
+ this.mLogSessionId = Objects.requireNonNull(sessionId);
+ }
+
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
// Private methods.
private MediaParser(
@@ -2184,6 +2197,7 @@
// Native methods.
private native void nativeSubmitMetrics(
+ // TODO: String logSessionId,
String parserName,
boolean createdByName,
String parserPool,
diff --git a/core/api/current.txt b/core/api/current.txt
index 96bc26c..24eca01 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1179,6 +1179,7 @@
field public static final int reqNavigation = 16843306; // 0x101022a
field public static final int reqTouchScreen = 16843303; // 0x1010227
field public static final int requestLegacyExternalStorage = 16844291; // 0x1010603
+ field public static final int requestOptimizedExternalStorageAccess = 16844357; // 0x1010645
field public static final int requireDeviceScreenOn = 16844317; // 0x101061d
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
@@ -6319,13 +6320,13 @@
method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int);
method public static android.app.PendingIntent getActivity(android.content.Context, int, @NonNull android.content.Intent, int, @Nullable android.os.Bundle);
method public static android.app.PendingIntent getBroadcast(android.content.Context, int, @NonNull android.content.Intent, int);
- method @NonNull public String getCreatorPackage();
+ method @Nullable public String getCreatorPackage();
method public int getCreatorUid();
method @NonNull public android.os.UserHandle getCreatorUserHandle();
method public static android.app.PendingIntent getForegroundService(android.content.Context, int, @NonNull android.content.Intent, int);
method @NonNull public android.content.IntentSender getIntentSender();
method public static android.app.PendingIntent getService(android.content.Context, int, @NonNull android.content.Intent, int);
- method @Deprecated @NonNull public String getTargetPackage();
+ method @Deprecated @Nullable public String getTargetPackage();
method public boolean isActivity();
method public boolean isBroadcast();
method public boolean isForegroundService();
@@ -20277,6 +20278,7 @@
method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
method public android.media.AudioDeviceInfo[] getDevices(int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public int getEncodedSurroundMode();
method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
method public int getMode();
method public String getParameters(String);
@@ -20299,6 +20301,7 @@
method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
method public boolean isSpeakerphoneOn();
method public boolean isStreamMute(int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean isSurroundFormatEnabled(int);
method public boolean isVolumeFixed();
method @Deprecated public boolean isWiredHeadsetOn();
method public void loadSoundEffects();
@@ -20318,6 +20321,7 @@
method @Deprecated public void setBluetoothA2dpOn(boolean);
method public void setBluetoothScoOn(boolean);
method public boolean setCommunicationDevice(@NonNull android.media.AudioDeviceInfo);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setEncodedSurroundMode(int);
method public void setMicrophoneMute(boolean);
method public void setMode(int);
method public void setParameters(String);
@@ -20327,6 +20331,7 @@
method @Deprecated public void setStreamMute(int, boolean);
method @Deprecated public void setStreamSolo(int, boolean);
method public void setStreamVolume(int, int, int);
+ method @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public boolean setSurroundFormatEnabled(int, boolean);
method @Deprecated public void setVibrateSetting(int, int);
method @Deprecated public void setWiredHeadsetOn(boolean);
method @Deprecated public boolean shouldVibrate(int);
@@ -20365,6 +20370,10 @@
field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
+ field public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; // 0x2
+ field public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; // 0x0
+ field public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; // 0x3
+ field public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1; // 0x1
field public static final int ERROR = -1; // 0xffffffff
field public static final int ERROR_DEAD_OBJECT = -6; // 0xfffffffa
field public static final String EXTRA_AUDIO_PLUG_STATE = "android.media.extra.AUDIO_PLUG_STATE";
@@ -20578,6 +20587,7 @@
method public int getChannelConfiguration();
method public int getChannelCount();
method @NonNull public android.media.AudioFormat getFormat();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
method public int getNotificationMarkerPosition();
@@ -20600,6 +20610,7 @@
method public void release();
method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public int setNotificationMarkerPosition(int);
method public int setPositionNotificationPeriod(int);
method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
@@ -20715,6 +20726,7 @@
method public int getChannelCount();
method public int getDualMonoMode();
method @NonNull public android.media.AudioFormat getFormat();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public static float getMaxVolume();
method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
@@ -20752,6 +20764,7 @@
method public int setAuxEffectSendLevel(@FloatRange(from=0.0) float);
method public int setBufferSizeInFrames(@IntRange(from=0) int);
method public boolean setDualMonoMode(int);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public int setLoopPoints(@IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
method public int setNotificationMarkerPosition(int);
method public void setOffloadDelayPadding(@IntRange(from=0) int, @IntRange(from=0) int);
@@ -21306,7 +21319,7 @@
method @NonNull public String getDiagnosticInfo();
}
- public final class MediaCodec implements android.media.metrics.PlaybackComponent {
+ public final class MediaCodec {
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, @Nullable android.media.MediaCrypto, int);
method public void configure(@Nullable android.media.MediaFormat, @Nullable android.view.Surface, int, @Nullable android.media.MediaDescrambler);
method @NonNull public static android.media.MediaCodec createByCodecName(@NonNull String) throws java.io.IOException;
@@ -21332,7 +21345,6 @@
method @NonNull public android.media.MediaFormat getOutputFormat(int);
method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
method @Nullable public android.media.Image getOutputImage(int);
- method public String getPlaybackId();
method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
method @Nullable public static android.media.Image mapHardwareBuffer(@NonNull android.hardware.HardwareBuffer);
method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
@@ -21349,7 +21361,6 @@
method public void setOnFrameRenderedListener(@Nullable android.media.MediaCodec.OnFrameRenderedListener, @Nullable android.os.Handler);
method public void setOutputSurface(@NonNull android.view.Surface);
method public void setParameters(@Nullable android.os.Bundle);
- method public void setPlaybackId(@NonNull String);
method public void setVideoScalingMode(int);
method public void signalEndOfInputStream();
method public void start();
@@ -21963,7 +21974,7 @@
method @NonNull public java.util.List<byte[]> getOfflineLicenseKeySetIds();
method public int getOfflineLicenseState(@NonNull byte[]);
method public int getOpenSessionCount();
- method @Nullable public android.media.metrics.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
+ method @Nullable public android.media.MediaDrm.PlaybackComponent getPlaybackComponent(@NonNull byte[]);
method @NonNull public byte[] getPropertyByteArray(String);
method @NonNull public String getPropertyString(@NonNull String);
method @NonNull public android.media.MediaDrm.ProvisionRequest getProvisionRequest();
@@ -22168,6 +22179,11 @@
method public void onSessionLostState(@NonNull android.media.MediaDrm, @NonNull byte[]);
}
+ public final class MediaDrm.PlaybackComponent {
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
+ }
+
public static final class MediaDrm.ProvisionRequest {
method @NonNull public byte[] getData();
method @NonNull public String getDefaultUrl();
@@ -22200,6 +22216,7 @@
method public long getCachedDuration();
method public android.media.MediaExtractor.CasInfo getCasInfo(int);
method public android.media.DrmInitData getDrmInitData();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public android.os.PersistableBundle getMetrics();
method @Nullable public java.util.Map<java.util.UUID,byte[]> getPsshInfo();
method public boolean getSampleCryptoInfo(@NonNull android.media.MediaCodec.CryptoInfo);
@@ -22221,6 +22238,7 @@
method public void setDataSource(@NonNull android.content.res.AssetFileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public void setMediaCas(@NonNull android.media.MediaCas);
method public void unselectTrack(int);
field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
@@ -22824,6 +22842,7 @@
method public java.util.List<android.media.MicrophoneInfo> getActiveMicrophones() throws java.io.IOException;
method @Nullable public android.media.AudioRecordingConfiguration getActiveRecordingConfiguration();
method public static final int getAudioSourceMax();
+ method @NonNull public android.media.metrics.LogSessionId getLogSessionId();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
method public android.os.PersistableBundle getMetrics();
method public android.media.AudioDeviceInfo getPreferredDevice();
@@ -22846,6 +22865,7 @@
method public void setCaptureRate(double);
method public void setInputSurface(@NonNull android.view.Surface);
method public void setLocation(float, float);
+ method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
method public void setMaxDuration(int) throws java.lang.IllegalArgumentException;
method public void setMaxFileSize(long) throws java.lang.IllegalArgumentException;
method public void setNextOutputFile(java.io.FileDescriptor) throws java.io.IOException;
@@ -24477,6 +24497,8 @@
}
public final class LogSessionId {
+ method @NonNull public String getStringId();
+ field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE;
}
public class MediaMetricsManager {
@@ -24510,11 +24532,6 @@
method @NonNull public android.media.metrics.NetworkEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=0xffffffff) long);
}
- public interface PlaybackComponent {
- method @NonNull public String getPlaybackId();
- method public void setPlaybackId(@NonNull String);
- }
-
public final class PlaybackErrorEvent extends android.media.metrics.Event implements android.os.Parcelable {
method public int describeContents();
method public int getErrorCode();
@@ -34918,7 +34935,7 @@
field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
- field public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS = "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+ field public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS = "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
@@ -35550,6 +35567,8 @@
field public static final String AUTHORITY = "service-state";
field public static final android.net.Uri CONTENT_URI;
field public static final String DATA_NETWORK_TYPE = "data_network_type";
+ field public static final String DATA_REG_STATE = "data_reg_state";
+ field public static final String DUPLEX_MODE = "duplex_mode";
field public static final String IS_MANUAL_NETWORK_SELECTION = "is_manual_network_selection";
field public static final String VOICE_OPERATOR_NUMERIC = "voice_operator_numeric";
field public static final String VOICE_REG_STATE = "voice_reg_state";
@@ -37457,6 +37476,7 @@
method public void onDisconnected();
method public abstract void onFillRequest(@NonNull android.service.autofill.FillRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
method public abstract void onSaveRequest(@NonNull android.service.autofill.SaveRequest, @NonNull android.service.autofill.SaveCallback);
+ method public void onSavedDatasetsInfoRequest(@NonNull android.service.autofill.SavedDatasetsInfoCallback);
field public static final String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final String SERVICE_META_DATA = "android.autofill";
}
@@ -37732,6 +37752,22 @@
field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.SaveRequest> CREATOR;
}
+ public final class SavedDatasetsInfo {
+ ctor public SavedDatasetsInfo(@NonNull String, @IntRange(from=0) int);
+ method @IntRange(from=0) public int getCount();
+ method @NonNull public String getType();
+ field public static final String TYPE_OTHER = "other";
+ field public static final String TYPE_PASSWORDS = "passwords";
+ }
+
+ public interface SavedDatasetsInfoCallback {
+ method public void onError(int);
+ method public void onSuccess(@NonNull java.util.Set<android.service.autofill.SavedDatasetsInfo>);
+ field public static final int ERROR_NEEDS_USER_ACTION = 2; // 0x2
+ field public static final int ERROR_OTHER = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED = 1; // 0x1
+ }
+
public final class TextValueSanitizer implements android.os.Parcelable android.service.autofill.Sanitizer {
ctor public TextValueSanitizer(@NonNull java.util.regex.Pattern, @NonNull String);
method public int describeContents();
@@ -40740,6 +40776,7 @@
field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int";
field public static final String KEY_PREFIX = "ims.";
field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool";
+ field public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY = "ims.rcs_feature_tag_allowed_string_array";
field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int";
}
@@ -42344,7 +42381,7 @@
field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
field public static final int OVERRIDE_NETWORK_TYPE_LTE_CA = 1; // 0x1
field public static final int OVERRIDE_NETWORK_TYPE_NONE = 0; // 0x0
- field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4; // 0x4
+ field public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5; // 0x5
field public static final int OVERRIDE_NETWORK_TYPE_NR_NSA = 3; // 0x3
field @Deprecated public static final int OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE = 4; // 0x4
}
@@ -48096,6 +48133,7 @@
}
public static final class SurfaceControlViewHost.SurfacePackage implements android.os.Parcelable {
+ ctor public SurfaceControlViewHost.SurfacePackage(@NonNull android.view.SurfaceControlViewHost.SurfacePackage);
method public int describeContents();
method public void release();
method public void writeToParcel(@NonNull android.os.Parcel, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 8dc5c15..5925b72 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -201,14 +201,6 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int);
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback);
method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void unregisterNetworkPolicyCallback(@NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback);
- field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
- field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
- field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
- field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
- field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
- field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
- field public static final int BLOCKED_REASON_NONE = 0; // 0x0
- field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
}
public static interface NetworkPolicyManager.NetworkPolicyCallback {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index edc6ab8..2094e98 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -92,6 +92,7 @@
field public static final String CREATE_USERS = "android.permission.CREATE_USERS";
field public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
field public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
+ field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
@@ -311,6 +312,7 @@
field public static final int hotwordDetectionService = 16844326; // 0x1010626
field public static final int isVrOnly = 16844152; // 0x1010578
field public static final int minExtensionVersion = 16844305; // 0x1010611
+ field public static final int playHomeTransitionSound = 16844358; // 0x1010646
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
field public static final int sdkVersion = 16844304; // 0x1010610
@@ -1838,7 +1840,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);
@@ -2445,6 +2447,7 @@
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+ method @Nullable public Boolean hasRequestOptimizedExternalStorageAccess();
method public boolean isEncryptionAware();
method public boolean isInstantApp();
method public boolean isOem();
@@ -4449,6 +4452,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();
@@ -4464,6 +4468,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);
@@ -5432,6 +5437,8 @@
public final class MediaSessionManager {
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventDispatchedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
+ method @Nullable @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.media.session.MediaSession.Token getMediaKeyEventSession();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public String getMediaKeyEventSessionPackageName();
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventDispatchedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventDispatchedListener);
method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
method @RequiresPermission(android.Manifest.permission.SET_MEDIA_KEY_LISTENER) public void setOnMediaKeyListener(android.media.session.MediaSessionManager.OnMediaKeyListener, @Nullable android.os.Handler);
@@ -7570,9 +7577,11 @@
method public void notifyAlertReached();
method public void notifyLimitReached();
method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+ method public void notifyWarningReached();
method public abstract void onRequestStatsUpdate(int);
method public abstract void onSetAlert(long);
method public abstract void onSetLimit(@NonNull String, long);
+ method public void onSetWarningAndLimit(@NonNull String, long, long);
field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
}
@@ -7780,6 +7789,7 @@
method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String);
method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback);
+ method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback);
method public void setOnServiceDeadCallback(@NonNull Runnable);
method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback);
@@ -7792,6 +7802,7 @@
method public boolean tearDownClientInterface(@NonNull String);
method public boolean tearDownInterfaces();
method public boolean tearDownSoftApInterface(@NonNull String);
+ method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener);
field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR";
field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1
field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0
@@ -7802,6 +7813,10 @@
field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
}
+ public static interface WifiNl80211Manager.CountryCodeChangeListener {
+ method public void onChanged(@NonNull String);
+ }
+
public static class WifiNl80211Manager.OemSecurityType {
ctor public WifiNl80211Manager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
field public final int groupCipher;
@@ -8676,9 +8691,15 @@
method @WorkerThread public void allocateBytes(@NonNull java.util.UUID, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public void allocateBytes(java.io.FileDescriptor, long, @RequiresPermission int) throws java.io.IOException;
method @WorkerThread public long getAllocatableBytes(@NonNull java.util.UUID, @RequiresPermission int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public int getExternalStorageMountMode(int, @NonNull String);
method public static boolean hasIsolatedStorage();
method public void updateExternalStorageFileQuotaType(@NonNull java.io.File, int) throws java.io.IOException;
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
+ field public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE = 4; // 0x4
+ field public static final int MOUNT_MODE_EXTERNAL_DEFAULT = 1; // 0x1
+ field public static final int MOUNT_MODE_EXTERNAL_INSTALLER = 2; // 0x2
+ field public static final int MOUNT_MODE_EXTERNAL_NONE = 0; // 0x0
+ field public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = 3; // 0x3
field public static final int QUOTA_TYPE_MEDIA_AUDIO = 2; // 0x2
field public static final int QUOTA_TYPE_MEDIA_IMAGE = 1; // 0x1
field public static final int QUOTA_TYPE_MEDIA_NONE = 0; // 0x0
@@ -8925,6 +8946,7 @@
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
+ field public static final String NAMESPACE_APPSEARCH = "appsearch";
field public static final String NAMESPACE_APP_COMPAT = "app_compat";
field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
@@ -9002,6 +9024,8 @@
method @NonNull public static android.net.Uri setManageMode(@NonNull android.net.Uri);
field public static final String ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
field public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT";
+ field public static final String DOWNLOADS_PROVIDER_AUTHORITY = "downloads";
+ field public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents";
field public static final String EXTRA_SHOW_ADVANCED = "android.provider.extra.SHOW_ADVANCED";
}
@@ -12281,6 +12305,20 @@
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.EpsBearerQosSessionAttributes> CREATOR;
}
+ public final class NrQosSessionAttributes implements android.os.Parcelable android.net.QosSessionAttributes {
+ method public int describeContents();
+ method public int get5Qi();
+ method public long getAveragingWindow();
+ method public long getGuaranteedDownlinkBitRate();
+ method public long getGuaranteedUplinkBitRate();
+ method public long getMaxDownlinkBitRate();
+ method public long getMaxUplinkBitRate();
+ method public int getQfi();
+ method @NonNull public java.util.List<java.net.InetSocketAddress> getRemoteAddresses();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.data.NrQosSessionAttributes> CREATOR;
+ }
+
public abstract class QualifiedNetworksService extends android.app.Service {
ctor public QualifiedNetworksService();
method @NonNull public abstract android.telephony.data.QualifiedNetworksService.NetworkAvailabilityProvider onCreateNetworkAvailabilityProvider(int);
@@ -14277,13 +14315,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/api/test-current.txt b/core/api/test-current.txt
index b61004b..97ad48c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -233,6 +233,8 @@
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls";
+ field public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
+ field public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier";
field public static final int OP_COARSE_LOCATION = 0; // 0x0
field public static final int OP_RECORD_AUDIO = 27; // 0x1b
@@ -1478,7 +1480,7 @@
package android.media.metrics {
public final class LogSessionId {
- method @NonNull public String getStringId();
+ ctor public LogSessionId(@NonNull String);
}
}
@@ -1931,6 +1933,17 @@
package android.permission {
+ public final class PermGroupUsage {
+ ctor public PermGroupUsage(@NonNull String, int, @NonNull String, long, boolean, boolean, @Nullable CharSequence);
+ method @Nullable public CharSequence getAttribution();
+ method public long getLastAccess();
+ method @NonNull public String getPackageName();
+ method @NonNull public String getPermGroupName();
+ method public int getUid();
+ method public boolean isActive();
+ method public boolean isPhoneCall();
+ }
+
public final class PermissionControllerManager {
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void countPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull android.permission.PermissionControllerManager.OnCountPermissionAppsResultCallback, @Nullable android.os.Handler);
method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
@@ -1945,6 +1958,10 @@
method public void onGetAppPermissions(@NonNull java.util.List<android.permission.RuntimePermissionPresentationInfo>);
}
+ public final class PermissionManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
+ }
+
}
package android.print {
@@ -2333,6 +2350,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
method public int getDataNetworkType();
+ method public int getDataRegState();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index a6aa28e..7e4af1a 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1561,12 +1561,14 @@
*
* @hide
*/
+ @TestApi
public static final String OPSTR_PHONE_CALL_MICROPHONE = "android:phone_call_microphone";
/**
* Phone call is using camera
*
* @hide
*/
+ @TestApi
public static final String OPSTR_PHONE_CALL_CAMERA = "android:phone_call_camera";
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0358fe5..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;
@@ -1995,8 +1999,8 @@
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
+ " from a non-visual Context:" + getOuterContext();
- final String message = "Visual services, such as WindowManager"
- + "or LayoutInflater should be accessed from Activity or other visual "
+ final String message = "Visual services, such as WindowManager "
+ + "or LayoutInflater should be accessed from Activity or another visual "
+ "Context. Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to "
+ "the configuration and visual bounds of an area on screen.";
@@ -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/Notification.java b/core/java/android/app/Notification.java
index 6472913..420ec08 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5283,8 +5283,7 @@
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors except when low-priority mode prevents that
if (!p.mReduceHighlights) {
- textColor = getBackgroundColor(p);
- pillColor = getAccentColor(p);
+ pillColor = getAccentTertiaryColor(p);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6219,6 +6218,25 @@
}
/**
+ * Gets the tertiary accent color for colored UI elements. If we're tinting with the theme
+ * accent, this comes from the tertiary system accent palette, otherwise this would be
+ * identical to {@link #getSmallIconColor(StandardTemplateParams)}.
+ */
+ private @ColorInt int getAccentTertiaryColor(StandardTemplateParams p) {
+ if (isColorized(p)) {
+ return getPrimaryTextColor(p);
+ }
+ if (mTintWithThemeAccent) {
+ int color = obtainThemeColor(com.android.internal.R.attr.colorAccentTertiary,
+ COLOR_INVALID);
+ if (color != COLOR_INVALID) {
+ return color;
+ }
+ }
+ return getContrastColor(p);
+ }
+
+ /**
* Gets the theme's error color, or the primary text color for colorized notifications.
*/
private @ColorInt int getErrorColor(StandardTemplateParams p) {
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 11adc5a..f4b9542 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -1010,7 +1010,7 @@
* @deprecated Renamed to {@link #getCreatorPackage()}.
*/
@Deprecated
- @NonNull
+ @Nullable
public String getTargetPackage() {
return getCreatorPackage();
}
@@ -1032,7 +1032,7 @@
*
* @return The package name of the PendingIntent.
*/
- @NonNull
+ @Nullable
public String getCreatorPackage() {
return getCachedInfo().getCreatorPackage();
}
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/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 3ef6757..6a71c92 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1962,14 +1962,20 @@
}
/**
- * Set the current zoom out level of the wallpaper
+ * Set the current zoom out level of the wallpaper.
+ *
+ * @param windowToken window requesting wallpaper zoom. Zoom level will only be applier while
+ * such window is visible.
* @param zoom from 0 to 1 (inclusive) where 1 means fully zoomed out, 0 means fully zoomed in
*
* @hide
*/
- public void setWallpaperZoomOut(IBinder windowToken, float zoom) {
+ public void setWallpaperZoomOut(@NonNull IBinder windowToken, float zoom) {
if (zoom < 0 || zoom > 1f) {
- throw new IllegalArgumentException("zoom must be between 0 and one: " + zoom);
+ throw new IllegalArgumentException("zoom must be between 0 and 1: " + zoom);
+ }
+ if (windowToken == null) {
+ throw new IllegalArgumentException("windowToken must not be null");
}
try {
WindowManagerGlobal.getWindowSession().setWallpaperZoomOut(windowToken, zoom);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 117df02..d3534f9 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -872,8 +872,7 @@
*
* The name is displayed only during provisioning.
*
- * <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
- * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
+ * <p>Use in an intent with action {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
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/content/OWNERS b/core/java/android/content/OWNERS
index d0d406a..01b554a 100644
--- a/core/java/android/content/OWNERS
+++ b/core/java/android/content/OWNERS
@@ -8,3 +8,5 @@
per-file AutofillOptions* = file:/core/java/android/service/autofill/OWNERS
per-file ContentCaptureOptions* = file:/core/java/android/service/contentcapture/OWNERS
per-file LocusId* = file:/core/java/android/service/contentcapture/OWNERS
+per-file ComponentCallbacksController = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksController = charlesccchen@google.com
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index feb58a30..0952b3e 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -550,9 +550,18 @@
public static final int FLAG_INHERIT_SHOW_WHEN_LOCKED = 0x1;
/**
+ * Bit in {@link #privateFlags} indicating whether a home sound effect should be played if the
+ * home app moves to front after the activity with this flag set.
+ * Set from the {@link android.R.attr#playHomeTransitionSound} attribute.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_HOME_TRANSITION_SOUND = 0x2;
+
+ /**
* Options that have been set in the activity declaration in the manifest.
* These include:
- * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED}.
+ * {@link #FLAG_INHERIT_SHOW_WHEN_LOCKED},
+ * {@link #PRIVATE_FLAG_HOME_TRANSITION_SOUND}.
* @hide
*/
public int privateFlags;
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1a5dad5..0da453d 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1411,6 +1411,13 @@
private Boolean nativeHeapZeroInit;
/**
+ * If {@code true} this app requests optimized external storage access.
+ * The request may not be honored due to policy or other reasons.
+ */
+ @Nullable
+ private Boolean requestOptimizedExternalStorageAccess;
+
+ /**
* Represents the default policy. The actual policy used will depend on other properties of
* the application, e.g. the target SDK version.
* @hide
@@ -1566,6 +1573,10 @@
if (nativeHeapZeroInit != null) {
pw.println(prefix + "nativeHeapZeroInit=" + nativeHeapZeroInit);
}
+ if (requestOptimizedExternalStorageAccess != null) {
+ pw.println(prefix + "requestOptimizedExternalStorageAccess="
+ + requestOptimizedExternalStorageAccess);
+ }
}
super.dumpBack(pw, prefix);
}
@@ -1792,6 +1803,7 @@
gwpAsanMode = orig.gwpAsanMode;
memtagMode = orig.memtagMode;
nativeHeapZeroInit = orig.nativeHeapZeroInit;
+ requestOptimizedExternalStorageAccess = orig.requestOptimizedExternalStorageAccess;
}
public String toString() {
@@ -1880,6 +1892,7 @@
dest.writeInt(gwpAsanMode);
dest.writeInt(memtagMode);
sForBoolean.parcel(nativeHeapZeroInit, dest, parcelableFlags);
+ sForBoolean.parcel(requestOptimizedExternalStorageAccess, dest, parcelableFlags);
}
public static final @android.annotation.NonNull Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1965,6 +1978,7 @@
gwpAsanMode = source.readInt();
memtagMode = source.readInt();
nativeHeapZeroInit = sForBoolean.unparcel(source);
+ requestOptimizedExternalStorageAccess = sForBoolean.unparcel(source);
}
/**
@@ -2079,6 +2093,24 @@
}
/**
+ * @return
+ * <ul>
+ * <li>{@code true} if this app requested optimized external storage access
+ * <li>{@code false} if this app requests to disable optimized external storage access.
+ * <li>{@code null} if the app didn't specify
+ * {@link android.R.styleable#AndroidManifestApplication_requestOptimizedExternalStorageAccess}
+ * in its manifest file.
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public Boolean hasRequestOptimizedExternalStorageAccess() {
+ return requestOptimizedExternalStorageAccess;
+ }
+
+ /**
* If {@code true} this app allows heap pointer tagging.
*
* @hide
@@ -2351,6 +2383,10 @@
/** {@hide} */ public void setGwpAsanMode(@GwpAsanMode int value) { gwpAsanMode = value; }
/** {@hide} */ public void setMemtagMode(@MemtagMode int value) { memtagMode = value; }
/** {@hide} */ public void setNativeHeapZeroInit(@Nullable Boolean value) { nativeHeapZeroInit = value; }
+ /** {@hide} */
+ public void setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+ requestOptimizedExternalStorageAccess = value;
+ }
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java
index ba6416d..4dc9ce8 100644
--- a/core/java/android/content/pm/parsing/ParsingPackage.java
+++ b/core/java/android/content/pm/parsing/ParsingPackage.java
@@ -257,6 +257,9 @@
ParsingPackage setNativeHeapZeroInit(@Nullable Boolean nativeHeapZeroInit);
+ ParsingPackage setRequestOptimizedExternalStorageAccess(
+ @Nullable Boolean requestOptimizedExternalStorageAccess);
+
ParsingPackage setCrossProfile(boolean crossProfile);
ParsingPackage setFullBackupContent(int fullBackupContent);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index b3c26ab..065ed2e 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -389,6 +389,10 @@
@DataClass.ParcelWith(ForBoolean.class)
private Boolean nativeHeapZeroInit;
+ @Nullable
+ @DataClass.ParcelWith(ForBoolean.class)
+ private Boolean requestOptimizedExternalStorageAccess;
+
// TODO(chiuwinson): Non-null
@Nullable
private ArraySet<String> mimeGroups;
@@ -1068,6 +1072,7 @@
appInfo.setGwpAsanMode(gwpAsanMode);
appInfo.setMemtagMode(memtagMode);
appInfo.setNativeHeapZeroInit(nativeHeapZeroInit);
+ appInfo.setRequestOptimizedExternalStorageAccess(requestOptimizedExternalStorageAccess);
appInfo.setBaseCodePath(mBaseApkPath);
appInfo.setBaseResourcePath(mBaseApkPath);
appInfo.setCodePath(mPath);
@@ -1203,6 +1208,7 @@
dest.writeMap(this.mProperties);
dest.writeInt(this.memtagMode);
sForBoolean.parcel(this.nativeHeapZeroInit, dest, flags);
+ sForBoolean.parcel(this.requestOptimizedExternalStorageAccess, dest, flags);
}
public ParsingPackageImpl(Parcel in) {
@@ -1326,6 +1332,7 @@
this.mProperties = in.createTypedArrayMap(Property.CREATOR);
this.memtagMode = in.readInt();
this.nativeHeapZeroInit = sForBoolean.unparcel(in);
+ this.requestOptimizedExternalStorageAccess = sForBoolean.unparcel(in);
assignDerivedFields();
}
@@ -2105,6 +2112,12 @@
return nativeHeapZeroInit;
}
+ @Nullable
+ @Override
+ public Boolean hasRequestOptimizedExternalStorageAccess() {
+ return requestOptimizedExternalStorageAccess;
+ }
+
@Override
public boolean isPartiallyDirectBootAware() {
return getBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE);
@@ -2555,6 +2568,11 @@
}
@Override
+ public ParsingPackageImpl setRequestOptimizedExternalStorageAccess(@Nullable Boolean value) {
+ requestOptimizedExternalStorageAccess = value;
+ return this;
+ }
+ @Override
public ParsingPackageImpl setPartiallyDirectBootAware(boolean value) {
return setBoolean(Booleans.PARTIALLY_DIRECT_BOOT_AWARE, value);
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java
index 9f52183..47dfa9d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageRead.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java
@@ -902,6 +902,9 @@
@Nullable
Boolean isNativeHeapZeroInit();
+ @Nullable
+ Boolean hasRequestOptimizedExternalStorageAccess();
+
// TODO(b/135203078): Hide and enforce going through PackageInfoUtils
ApplicationInfo toAppInfoWithoutState();
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 2be0157..0e1574c 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -2012,6 +2012,12 @@
pkg.setNativeHeapZeroInit(sa.getBoolean(
R.styleable.AndroidManifestApplication_nativeHeapZeroInit, false));
}
+ if (sa.hasValue(
+ R.styleable.AndroidManifestApplication_requestOptimizedExternalStorageAccess)) {
+ pkg.setRequestOptimizedExternalStorageAccess(sa.getBoolean(R.styleable
+ .AndroidManifestApplication_requestOptimizedExternalStorageAccess,
+ false));
+ }
} finally {
sa.recycle();
}
diff --git a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
index d99c410..ff6aaad 100644
--- a/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
+++ b/core/java/android/content/pm/parsing/component/ParsedActivityUtils.java
@@ -149,7 +149,10 @@
| flag(ActivityInfo.FLAG_TURN_SCREEN_ON, R.styleable.AndroidManifestActivity_turnScreenOn, sa)
| flag(ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING, R.styleable.AndroidManifestActivity_preferMinimalPostProcessing, sa);
- activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED, R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa);
+ activity.privateFlags |= flag(ActivityInfo.FLAG_INHERIT_SHOW_WHEN_LOCKED,
+ R.styleable.AndroidManifestActivity_inheritShowWhenLocked, sa)
+ | flag(ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND,
+ R.styleable.AndroidManifestActivity_playHomeTransitionSound, true, sa);
activity.colorMode = sa.getInt(R.styleable.AndroidManifestActivity_colorMode, ActivityInfo.COLOR_MODE_DEFAULT);
activity.documentLaunchMode = sa.getInt(R.styleable.AndroidManifestActivity_documentLaunchMode, ActivityInfo.DOCUMENT_LAUNCH_NONE);
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index aed0823..ac4b7b7 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -360,7 +360,7 @@
WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
if (theme != null) {
- theme.setImpl(mResourcesImpl.newThemeImpl(theme.getKey()));
+ theme.setNewResourcesImpl(mResourcesImpl);
}
}
}
@@ -1500,6 +1500,9 @@
* retrieve XML attributes with style and theme information applied.
*/
public final class Theme {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
@UnsupportedAppUsage
private ResourcesImpl.ThemeImpl mThemeImpl;
@@ -1507,7 +1510,15 @@
}
void setImpl(ResourcesImpl.ThemeImpl impl) {
- mThemeImpl = impl;
+ synchronized (mLock) {
+ mThemeImpl = impl;
+ }
+ }
+
+ void setNewResourcesImpl(ResourcesImpl resImpl) {
+ synchronized (mLock) {
+ mThemeImpl = resImpl.newThemeImpl(mThemeImpl.getKey());
+ }
}
/**
@@ -1528,7 +1539,9 @@
* if not already defined in the theme.
*/
public void applyStyle(int resId, boolean force) {
- mThemeImpl.applyStyle(resId, force);
+ synchronized (mLock) {
+ mThemeImpl.applyStyle(resId, force);
+ }
}
/**
@@ -1541,7 +1554,11 @@
* @param other The existing Theme to copy from.
*/
public void setTo(Theme other) {
- mThemeImpl.setTo(other.mThemeImpl);
+ synchronized (mLock) {
+ synchronized (other.mLock) {
+ mThemeImpl.setTo(other.mThemeImpl);
+ }
+ }
}
/**
@@ -1566,7 +1583,9 @@
*/
@NonNull
public TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[] attrs) {
- return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, 0);
+ }
}
/**
@@ -1594,7 +1613,9 @@
public TypedArray obtainStyledAttributes(@StyleRes int resId,
@NonNull @StyleableRes int[] attrs)
throws NotFoundException {
- return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, null, attrs, 0, resId);
+ }
}
/**
@@ -1650,7 +1671,10 @@
public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
- return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr, defStyleRes);
+ synchronized (mLock) {
+ return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr,
+ defStyleRes);
+ }
}
/**
@@ -1671,7 +1695,9 @@
@NonNull
@UnsupportedAppUsage
public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) {
- return mThemeImpl.resolveAttributes(this, values, attrs);
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttributes(this, values, attrs);
+ }
}
/**
@@ -1692,7 +1718,9 @@
* <var>outValue</var> is valid, else false.
*/
public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
- return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs);
+ synchronized (mLock) {
+ return mThemeImpl.resolveAttribute(resid, outValue, resolveRefs);
+ }
}
/**
@@ -1702,7 +1730,9 @@
* @hide
*/
public int[] getAllAttributes() {
- return mThemeImpl.getAllAttributes();
+ synchronized (mLock) {
+ return mThemeImpl.getAllAttributes();
+ }
}
/**
@@ -1738,7 +1768,9 @@
* @see ActivityInfo
*/
public @Config int getChangingConfigurations() {
- return mThemeImpl.getChangingConfigurations();
+ synchronized (mLock) {
+ return mThemeImpl.getChangingConfigurations();
+ }
}
/**
@@ -1749,23 +1781,31 @@
* @param prefix Text to prefix each line printed.
*/
public void dump(int priority, String tag, String prefix) {
- mThemeImpl.dump(priority, tag, prefix);
+ synchronized (mLock) {
+ mThemeImpl.dump(priority, tag, prefix);
+ }
}
// Needed by layoutlib.
/*package*/ long getNativeTheme() {
- return mThemeImpl.getNativeTheme();
+ synchronized (mLock) {
+ return mThemeImpl.getNativeTheme();
+ }
}
/*package*/ int getAppliedStyleResId() {
- return mThemeImpl.getAppliedStyleResId();
+ synchronized (mLock) {
+ return mThemeImpl.getAppliedStyleResId();
+ }
}
/**
* @hide
*/
public ThemeKey getKey() {
- return mThemeImpl.getKey();
+ synchronized (mLock) {
+ return mThemeImpl.getKey();
+ }
}
private String getResourceNameFromHexString(String hexString) {
@@ -1781,7 +1821,9 @@
*/
@ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true)
public String[] getTheme() {
- return mThemeImpl.getTheme();
+ synchronized (mLock) {
+ return mThemeImpl.getTheme();
+ }
}
/** @hide */
@@ -1800,7 +1842,9 @@
* {@link #applyStyle(int, boolean)}.
*/
public void rebase() {
- mThemeImpl.rebase();
+ synchronized (mLock) {
+ mThemeImpl.rebase();
+ }
}
/**
@@ -1862,12 +1906,14 @@
@NonNull
public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
@StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
- int[] stack = mThemeImpl.getAttributeResolutionStack(
- defStyleAttr, defStyleRes, explicitStyleRes);
- if (stack == null) {
- return new int[0];
- } else {
- return stack;
+ synchronized (mLock) {
+ int[] stack = mThemeImpl.getAttributeResolutionStack(
+ defStyleAttr, defStyleRes, explicitStyleRes);
+ if (stack == null) {
+ return new int[0];
+ } else {
+ return stack;
+ }
}
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index cbcdb37..553e11b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -1314,22 +1314,16 @@
}
void applyStyle(int resId, boolean force) {
- synchronized (mKey) {
- mAssets.applyStyleToTheme(mTheme, resId, force);
- mThemeResId = resId;
- mKey.append(resId, force);
- }
+ mAssets.applyStyleToTheme(mTheme, resId, force);
+ mThemeResId = resId;
+ mKey.append(resId, force);
}
void setTo(ThemeImpl other) {
- synchronized (mKey) {
- synchronized (other.mKey) {
- mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
+ mAssets.setThemeTo(mTheme, other.mAssets, other.mTheme);
- mThemeResId = other.mThemeResId;
- mKey.setTo(other.getKey());
- }
- }
+ mThemeResId = other.mThemeResId;
+ mKey.setTo(other.getKey());
}
@NonNull
@@ -1338,46 +1332,40 @@
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
- synchronized (mKey) {
- final int len = attrs.length;
- final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ final int len = attrs.length;
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- // XXX note that for now we only work with compiled XML files.
- // To support generic XML files we will need to manually parse
- // out the attributes from the XML file (applying type information
- // contained in the resources and such).
- final XmlBlock.Parser parser = (XmlBlock.Parser) set;
- mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
- array.mDataAddress, array.mIndicesAddress);
- array.mTheme = wrapper;
- array.mXml = parser;
- return array;
- }
+ // XXX note that for now we only work with compiled XML files.
+ // To support generic XML files we will need to manually parse
+ // out the attributes from the XML file (applying type information
+ // contained in the resources and such).
+ final XmlBlock.Parser parser = (XmlBlock.Parser) set;
+ mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+ array.mDataAddress, array.mIndicesAddress);
+ array.mTheme = wrapper;
+ array.mXml = parser;
+ return array;
}
@NonNull
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
@NonNull int[] values,
@NonNull int[] attrs) {
- synchronized (mKey) {
- final int len = attrs.length;
- if (values == null || len != values.length) {
- throw new IllegalArgumentException(
- "Base attribute values must the same length as attrs");
- }
-
- final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
- array.mTheme = wrapper;
- array.mXml = null;
- return array;
+ final int len = attrs.length;
+ if (values == null || len != values.length) {
+ throw new IllegalArgumentException(
+ "Base attribute values must the same length as attrs");
}
+
+ final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
+ mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+ array.mTheme = wrapper;
+ array.mXml = null;
+ return array;
}
boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) {
- synchronized (mKey) {
- return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
- }
+ return mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs);
}
int[] getAllAttributes() {
@@ -1385,35 +1373,29 @@
}
@Config int getChangingConfigurations() {
- synchronized (mKey) {
- final @NativeConfig int nativeChangingConfig =
- AssetManager.nativeThemeGetChangingConfigurations(mTheme);
- return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
- }
+ final @NativeConfig int nativeChangingConfig =
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
+ return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
}
public void dump(int priority, String tag, String prefix) {
- synchronized (mKey) {
- mAssets.dumpTheme(mTheme, priority, tag, prefix);
- }
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
}
String[] getTheme() {
- synchronized (mKey) {
- final int N = mKey.mCount;
- final String[] themes = new String[N * 2];
- for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) {
- final int resId = mKey.mResId[j];
- final boolean forced = mKey.mForce[j];
- try {
- themes[i] = getResourceName(resId);
- } catch (NotFoundException e) {
- themes[i] = Integer.toHexString(i);
- }
- themes[i + 1] = forced ? "forced" : "not forced";
+ final int n = mKey.mCount;
+ final String[] themes = new String[n * 2];
+ for (int i = 0, j = n - 1; i < themes.length; i += 2, --j) {
+ final int resId = mKey.mResId[j];
+ final boolean forced = mKey.mForce[j];
+ try {
+ themes[i] = getResourceName(resId);
+ } catch (NotFoundException e) {
+ themes[i] = Integer.toHexString(i);
}
- return themes;
+ themes[i + 1] = forced ? "forced" : "not forced";
}
+ return themes;
}
/**
@@ -1422,15 +1404,13 @@
* {@link #applyStyle(int, boolean)}.
*/
void rebase() {
- synchronized (mKey) {
- AssetManager.nativeThemeClear(mTheme);
+ AssetManager.nativeThemeClear(mTheme);
- // Reapply the same styles in the same order.
- for (int i = 0; i < mKey.mCount; i++) {
- final int resId = mKey.mResId[i];
- final boolean force = mKey.mForce[i];
- mAssets.applyStyleToTheme(mTheme, resId, force);
- }
+ // Reapply the same styles in the same order.
+ for (int i = 0; i < mKey.mCount; i++) {
+ final int resId = mKey.mResId[i];
+ final boolean force = mKey.mForce[i];
+ mAssets.applyStyleToTheme(mTheme, resId, force);
}
}
@@ -1455,10 +1435,8 @@
@Nullable
public int[] getAttributeResolutionStack(@AttrRes int defStyleAttr,
@StyleRes int defStyleRes, @StyleRes int explicitStyleRes) {
- synchronized (mKey) {
- return mAssets.getAttributeResolutionStack(
- mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
- }
+ return mAssets.getAttributeResolutionStack(
+ mTheme, defStyleAttr, defStyleRes, explicitStyleRes);
}
}
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 96e7d3b..799ea4a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,6 +18,7 @@
import static android.app.ActivityManager.procStateToString;
import static android.content.pm.PackageManager.GET_SIGNATURES;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -203,78 +204,6 @@
})
public @interface SubscriptionOverrideMask {}
- /**
- * Flag to indicate that an app is not subject to any restrictions that could result in its
- * network access blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_NONE = 0;
-
- /**
- * Flag to indicate that an app is subject to Battery saver restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0;
-
- /**
- * Flag to indicate that an app is subject to Doze restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_DOZE = 1 << 1;
-
- /**
- * Flag to indicate that an app is subject to App Standby restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2;
-
- /**
- * Flag to indicate that an app is subject to Restricted mode restrictions that would
- * result in its network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3;
-
- /**
- * Flag to indicate that an app is subject to Data saver restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16;
-
- /**
- * Flag to indicate that an app is subject to user restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17;
-
- /**
- * Flag to indicate that an app is subject to Device admin restrictions that would
- * result in its metered network access being blocked.
- *
- * @hide
- */
- @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18;
-
/** @hide */
public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000;
@@ -344,22 +273,6 @@
/** @hide */
public static final int ALLOWED_METERED_REASON_MASK = 0xffff0000;
- /**
- * @hide
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, prefix = {"BLOCKED_"}, value = {
- BLOCKED_REASON_NONE,
- BLOCKED_REASON_BATTERY_SAVER,
- BLOCKED_REASON_DOZE,
- BLOCKED_REASON_APP_STANDBY,
- BLOCKED_REASON_RESTRICTED_MODE,
- BLOCKED_METERED_REASON_DATA_SAVER,
- BLOCKED_METERED_REASON_USER_RESTRICTED,
- BLOCKED_METERED_REASON_ADMIN_DISABLED,
- })
- public @interface BlockedReason {}
-
private final Context mContext;
@UnsupportedAppUsage
private INetworkPolicyManager mService;
@@ -883,14 +796,15 @@
* {@code BLOCKED_REASON_*} and/or {@code BLOCKED_METERED_REASON_*} constants.
*
* @param blockedReasons Value indicating the reasons for why the network access of an UID is
- * blocked. If the value is equal to {@link #BLOCKED_REASON_NONE}, then
+ * blocked. If the value is equal to
+ * {@link ConnectivityManager#BLOCKED_REASON_NONE}, then
* it indicates that an app's network access is not blocked.
* @param meteredNetwork Value indicating whether the network is metered or not.
* @return Whether network access is blocked or not.
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- public static boolean isUidBlocked(@BlockedReason int blockedReasons, boolean meteredNetwork) {
+ public static boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
if (blockedReasons == BLOCKED_REASON_NONE) {
return false;
}
@@ -913,7 +827,7 @@
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
- public static String blockedReasonsToString(@BlockedReason int blockedReasons) {
+ public static String blockedReasonsToString(int blockedReasons) {
return DebugUtils.flagsToString(NetworkPolicyManager.class, "BLOCKED_", blockedReasons);
}
@@ -977,7 +891,7 @@
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
- default void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {}
+ default void onUidBlockedReasonChanged(int uid, int blockedReasons) {}
}
/** @hide */
@@ -992,8 +906,7 @@
}
@Override
- public void onBlockedReasonChanged(int uid, @BlockedReason int oldBlockedReasons,
- @BlockedReason int newBlockedReasons) {
+ public void onBlockedReasonChanged(int uid, int oldBlockedReasons, int newBlockedReasons) {
if (oldBlockedReasons != newBlockedReasons) {
dispatchOnUidBlockedReasonChanged(mExecutor, mCallback, uid, newBlockedReasons);
}
@@ -1001,7 +914,7 @@
}
private static void dispatchOnUidBlockedReasonChanged(@Nullable Executor executor,
- @NonNull NetworkPolicyCallback callback, int uid, @BlockedReason int blockedReasons) {
+ @NonNull NetworkPolicyCallback callback, int uid, int blockedReasons) {
if (executor == null) {
callback.onUidBlockedReasonChanged(uid, blockedReasons);
} else {
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
index 4078b24..74c3ba4 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -23,6 +23,6 @@
*/
oneway interface INetworkStatsProvider {
void onRequestStatsUpdate(int token);
- void onSetLimit(String iface, long quotaBytes);
void onSetAlert(long quotaBytes);
+ void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes);
}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
index bd336dd..7eaa01e 100644
--- a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -26,6 +26,6 @@
oneway interface INetworkStatsProviderCallback {
void notifyStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
void notifyAlertReached();
- void notifyLimitReached();
+ void notifyWarningOrLimitReached();
void unregister();
}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
index 7639d22..23fc069 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -29,7 +29,8 @@
@SystemApi
public abstract class NetworkStatsProvider {
/**
- * A value used by {@link #onSetLimit} and {@link #onSetAlert} indicates there is no limit.
+ * A value used by {@link #onSetLimit}, {@link #onSetAlert} and {@link #onSetWarningAndLimit}
+ * indicates there is no limit.
*/
public static final int QUOTA_UNLIMITED = -1;
@@ -42,13 +43,13 @@
}
@Override
- public void onSetLimit(String iface, long quotaBytes) {
- NetworkStatsProvider.this.onSetLimit(iface, quotaBytes);
+ public void onSetAlert(long quotaBytes) {
+ NetworkStatsProvider.this.onSetAlert(quotaBytes);
}
@Override
- public void onSetAlert(long quotaBytes) {
- NetworkStatsProvider.this.onSetAlert(quotaBytes);
+ public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {
+ NetworkStatsProvider.this.onSetWarningAndLimit(iface, warningBytes, limitBytes);
}
};
@@ -145,11 +146,25 @@
}
/**
- * Notify system that the quota set by {@code onSetLimit} has been reached.
+ * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
+ */
+ public void notifyWarningReached() {
+ try {
+ // Reuse the code path to notify warning reached with limit reached
+ // since framework handles them in the same way.
+ getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the quota set by {@link #onSetLimit} or limit set by
+ * {@link #onSetWarningAndLimit} has been reached.
*/
public void notifyLimitReached() {
try {
- getProviderCallbackBinderOrThrow().notifyLimitReached();
+ getProviderCallbackBinderOrThrow().notifyWarningOrLimitReached();
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -183,6 +198,28 @@
public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
/**
+ * Called by {@code NetworkStatsService} when setting the interface quotas for the specified
+ * upstream interface. If a provider implements {@link #onSetWarningAndLimit}, the system
+ * will not call {@link #onSetLimit}. When this method is called, the implementation
+ * should behave as follows:
+ * 1. If {@code warningBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider@notifyWarningReached()}.
+ * 2. If {@code limitBytes} is reached on {@code iface}, block all further traffic on
+ * {@code iface} and call {@link NetworkStatsProvider#notifyLimitReached()}.
+ *
+ * @param iface the interface requiring the operation.
+ * @param warningBytes the warning defined as the number of bytes, starting from zero and
+ * counting from now. A value of {@link #QUOTA_UNLIMITED} indicates
+ * there is no warning.
+ * @param limitBytes the limit defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+ */
+ public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
+ // Backward compatibility for those who didn't override this function.
+ onSetLimit(iface, limitBytes);
+ }
+
+ /**
* Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
* MUST call {@link NetworkStatsProvider#notifyAlertReached()} when {@code quotaBytes} bytes
* have been reached. Unlike {@link #onSetLimit(String, long)}, the custom implementation should
diff --git a/core/java/android/net/vcn/IVcnManagementService.aidl b/core/java/android/net/vcn/IVcnManagementService.aidl
index 6a3cb42..5b79f73 100644
--- a/core/java/android/net/vcn/IVcnManagementService.aidl
+++ b/core/java/android/net/vcn/IVcnManagementService.aidl
@@ -29,7 +29,7 @@
*/
interface IVcnManagementService {
void setVcnConfig(in ParcelUuid subscriptionGroup, in VcnConfig config, in String opPkgName);
- void clearVcnConfig(in ParcelUuid subscriptionGroup);
+ void clearVcnConfig(in ParcelUuid subscriptionGroup, in String opPkgName);
void addVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
void removeVcnUnderlyingNetworkPolicyListener(in IVcnUnderlyingNetworkPolicyListener listener);
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index c0189e2..abd41da 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -154,7 +154,7 @@
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
try {
- mService.clearVcnConfig(subscriptionGroup);
+ mService.clearVcnConfig(subscriptionGroup, mContext.getOpPackageName());
} catch (ServiceSpecificException e) {
throw new IOException(e);
} catch (RemoteException e) {
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index 9518bf1..85861bc 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -60,14 +60,18 @@
*/
public static final int FLAG_BATTERY_USAGE_STATS_INCLUDE_HISTORY = 2;
+ private static final long DEFAULT_MAX_STATS_AGE_MS = 5 * 60 * 1000;
+
private final int mFlags;
@NonNull
private final int[] mUserIds;
+ private final long mMaxStatsAgeMs;
private BatteryUsageStatsQuery(@NonNull Builder builder) {
mFlags = builder.mFlags;
mUserIds = builder.mUserIds != null ? builder.mUserIds.toArray()
: new int[]{UserHandle.USER_ALL};
+ mMaxStatsAgeMs = builder.mMaxStatsAgeMs;
}
@BatteryUsageStatsFlags
@@ -94,10 +98,19 @@
return (mFlags & FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL) != 0;
}
+ /**
+ * Returns the client's tolerance for stale battery stats. The data is allowed to be up to
+ * this many milliseconds out-of-date.
+ */
+ public long getMaxStatsAge() {
+ return mMaxStatsAgeMs;
+ }
+
private BatteryUsageStatsQuery(Parcel in) {
mFlags = in.readInt();
mUserIds = new int[in.readInt()];
in.readIntArray(mUserIds);
+ mMaxStatsAgeMs = in.readLong();
}
@Override
@@ -105,6 +118,7 @@
dest.writeInt(mFlags);
dest.writeInt(mUserIds.length);
dest.writeIntArray(mUserIds);
+ dest.writeLong(mMaxStatsAgeMs);
}
@Override
@@ -132,6 +146,7 @@
public static final class Builder {
private int mFlags;
private IntArray mUserIds;
+ private long mMaxStatsAgeMs = DEFAULT_MAX_STATS_AGE_MS;
/**
* Builds a read-only BatteryUsageStatsQuery object.
@@ -170,5 +185,14 @@
mFlags |= BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_POWER_PROFILE_MODEL;
return this;
}
+
+ /**
+ * Set the client's tolerance for stale battery stats. The data may be up to
+ * this many milliseconds out-of-date.
+ */
+ public Builder setMaxStatsAgeMs(long maxStatsAgeMs) {
+ mMaxStatsAgeMs = maxStatsAgeMs;
+ return this;
+ }
}
}
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index 81c38f8..9385402 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -202,4 +202,5 @@
void notifyAppIoResumed(in String volumeUuid, int uid, int tid, int reason) = 92;
PendingIntent getManageSpaceActivityIntent(in String packageName, int requestCode) = 93;
boolean isAppIoBlocked(in String volumeUuid, int uid, int tid, int reason) = 94;
- }
+ int getExternalStorageMountMode(int uid, in String packageName) = 95;
+}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index cae20ed..8107168 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2124,6 +2124,52 @@
}
}
+
+ /** @hide */
+ @IntDef(prefix = { "MOUNT_MODE_" }, value = {
+ MOUNT_MODE_EXTERNAL_NONE,
+ MOUNT_MODE_EXTERNAL_DEFAULT,
+ MOUNT_MODE_EXTERNAL_INSTALLER,
+ MOUNT_MODE_EXTERNAL_PASS_THROUGH,
+ MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE
+ })
+ /** @hide */
+ public @interface MountMode {}
+
+ /**
+ * No external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
+ /**
+ * Default external storage should be mounted.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_DEFAULT = IVold.REMOUNT_MODE_DEFAULT;
+ /**
+ * Mount mode for package installers which should give them access to
+ * all obb dirs in addition to their package sandboxes
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_INSTALLER = IVold.REMOUNT_MODE_INSTALLER;
+ /**
+ * The lower file system should be bind mounted directly on external storage
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+
+ /**
+ * Use the regular scoped storage filesystem, but Android/ should be writable.
+ * Used to support the applications hosting DownloadManager and the MTP server.
+ * @hide
+ */
+ @SystemApi
+ public static final int MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE =
+ IVold.REMOUNT_MODE_ANDROID_WRITABLE;
/**
* Flag indicating that a disk space allocation request should operate in an
* aggressive mode. This flag should only be rarely used in situations that
@@ -2301,6 +2347,28 @@
}
/**
+ * Returns the External Storage mount mode corresponding to the given uid and packageName.
+ * These mount modes specify different views and access levels for
+ * different apps on external storage.
+ *
+ * @params uid UID of the application
+ * @params packageName name of the package
+ * @return {@code MountMode} for the given uid and packageName.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+ @SystemApi
+ @MountMode
+ public int getExternalStorageMountMode(int uid, @NonNull String packageName) {
+ try {
+ return mStorageManager.getExternalStorageMountMode(uid, packageName);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Allocate the requested number of bytes for your application to use in the
* given open file. This will cause the system to delete any cached files
* necessary to satisfy your request.
diff --git a/core/java/android/permission/PermGroupUsage.java b/core/java/android/permission/PermGroupUsage.java
index c94c0ff..440d6f2 100644
--- a/core/java/android/permission/PermGroupUsage.java
+++ b/core/java/android/permission/PermGroupUsage.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
/**
* Represents the usage of a permission group by an app. Supports package name, user, permission
@@ -26,6 +27,7 @@
*
* @hide
*/
+@TestApi
public final class PermGroupUsage {
private final String mPackageName;
@@ -36,7 +38,19 @@
private final boolean mIsPhoneCall;
private final CharSequence mAttribution;
- PermGroupUsage(@NonNull String packageName, int uid,
+ /**
+ *
+ * @param packageName The package name of the using app
+ * @param uid The uid of the using app
+ * @param permGroupName The name of the permission group being used
+ * @param lastAccess The time of last access
+ * @param isActive Whether this is active
+ * @param isPhoneCall Whether this is a usage by the phone
+ * @param attribution An optional string attribution to show
+ * @hide
+ */
+ @TestApi
+ public PermGroupUsage(@NonNull String packageName, int uid,
@NonNull String permGroupName, long lastAccess, boolean isActive, boolean isPhoneCall,
@Nullable CharSequence attribution) {
this.mPackageName = packageName;
@@ -48,30 +62,58 @@
this.mAttribution = attribution;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @NonNull String getPackageName() {
return mPackageName;
}
+ /**
+ * @hide
+ */
+ @TestApi
public int getUid() {
return mUid;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @NonNull String getPermGroupName() {
return mPermGroupName;
}
+ /**
+ * @hide
+ */
+ @TestApi
public long getLastAccess() {
return mLastAccess;
}
+ /**
+ * @hide
+ */
+ @TestApi
public boolean isActive() {
return mIsActive;
}
+ /**
+ * @hide
+ */
+ @TestApi
public boolean isPhoneCall() {
return mIsPhoneCall;
}
+ /**
+ * @hide
+ */
+ @TestApi
public @Nullable CharSequence getAttribution() {
return mAttribution;
}
@@ -80,6 +122,7 @@
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this))
+ " packageName: " + mPackageName + ", UID: " + mUid + ", permGroup: "
- + mPermGroupName + ", isActive: " + mIsActive + ", attribution: " + mAttribution;
+ + mPermGroupName + ", lastAccess: " + mLastAccess + ", isActive: " + mIsActive
+ + ", attribution: " + mAttribution;
}
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 177e422..7669586cf 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -26,6 +26,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -851,6 +852,7 @@
*
* @hide
*/
+ @TestApi
@NonNull
@RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
public List<PermGroupUsage> getIndicatorAppOpUsageData() {
diff --git a/core/java/android/permission/PermissionUsageHelper.java b/core/java/android/permission/PermissionUsageHelper.java
index 921911b..2d6fa3c 100644
--- a/core/java/android/permission/PermissionUsageHelper.java
+++ b/core/java/android/permission/PermissionUsageHelper.java
@@ -71,13 +71,10 @@
private static final String PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled";
/** How long after an access to show it as "recent" */
- private static final String RECENT_ACCESS_TIME_MS = "recent_acccess_time_ms";
+ private static final String RECENT_ACCESS_TIME_MS = "recent_access_time_ms";
/** How long after an access to show it as "running" */
- private static final String RUNNING_ACCESS_TIME_MS = "running_acccess_time_ms";
-
- /** The name of the expected voice IME subtype */
- private static final String VOICE_IME_SUBTYPE = "voice";
+ private static final String RUNNING_ACCESS_TIME_MS = "running_access_time_ms";
private static final String SYSTEM_PKG = "android";
@@ -279,6 +276,10 @@
opEntry.getAttributedOpEntries().get(attributionTag);
long lastAccessTime = attrOpEntry.getLastAccessTime(opFlags);
+ if (attrOpEntry.isRunning()) {
+ lastAccessTime = now;
+ }
+
if (lastAccessTime < recentThreshold && !attrOpEntry.isRunning()) {
continue;
}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 7e40497..31cf63c 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -108,6 +108,13 @@
public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
/**
+ * Namespace for all AppSearch related features.
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_APPSEARCH = "appsearch";
+
+ /**
* Namespace for app standby configurations.
*
* @hide
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 0fea484..620fa65 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -236,10 +236,21 @@
public static final String
ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS";
- /** {@hide} */
+ /**
+ * External Storage Provider's authority string
+ * {@hide}
+ */
+ @SystemApi
public static final String EXTERNAL_STORAGE_PROVIDER_AUTHORITY =
"com.android.externalstorage.documents";
+ /**
+ * Download Manager's authority string
+ * {@hide}
+ */
+ @SystemApi
+ public static final String DOWNLOADS_PROVIDER_AUTHORITY = Downloads.Impl.AUTHORITY;
+
/** {@hide} */
public static final String EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID = "primary";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ca63fbd..719c383 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1068,8 +1068,8 @@
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_MANAGE_ALL_SUBSCRIPTIONS_SETTINGS =
- "android.settings.MANAGE_ALL_SUBSCRIPTIONS_SETTINGS";
+ public static final String ACTION_MANAGE_ALL_SIM_PROFILES_SETTINGS =
+ "android.settings.MANAGE_ALL_SIM_PROFILES_SETTINGS";
/**
* Activity Action: Show screen for controlling which apps can draw on top of other apps.
@@ -8523,6 +8523,15 @@
"one_handed_tutorial_show_count";
/**
+ * Indicates whether transform is enabled.
+ * <p>
+ * Type: int (0 for false, 1 for true)
+ *
+ * @hide
+ */
+ public static final String TRANSFORM_ENABLED = "transform_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 73208d3..38945f5 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4559,6 +4559,15 @@
public static final String VOICE_REG_STATE = "voice_reg_state";
/**
+ * An integer value indicating the current data service state.
+ * <p>
+ * Valid values: {@link ServiceState#STATE_IN_SERVICE},
+ * {@link ServiceState#STATE_OUT_OF_SERVICE}, {@link ServiceState#STATE_EMERGENCY_ONLY},
+ * {@link ServiceState#STATE_POWER_OFF}.
+ */
+ public static final String DATA_REG_STATE = "data_reg_state";
+
+ /**
* The current registered operator numeric id.
* <p>
* In GSM/UMTS, numeric format is 3 digit country code plus 2 or 3 digit
@@ -4581,6 +4590,17 @@
* This is the same as {@link TelephonyManager#getDataNetworkType()}.
*/
public static final String DATA_NETWORK_TYPE = "data_network_type";
+
+ /**
+ * An integer value indicating the current duplex mode if the radio technology is LTE,
+ * LTE-CA or NR.
+ * <p>
+ * Valid values: {@link ServiceState#DUPLEX_MODE_UNKNOWN},
+ * {@link ServiceState#DUPLEX_MODE_FDD}, {@link ServiceState#DUPLEX_MODE_TDD}.
+ * <p>
+ * This is the same as {@link ServiceState#getDuplexMode()}.
+ */
+ public static final String DUPLEX_MODE = "duplex_mode";
}
/**
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 04a4ca4..13274c6 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -38,6 +38,8 @@
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import com.android.internal.os.IResultReceiver;
+
/**
* An {@code AutofillService} is a service used to automatically fill the contents of the screen
* on behalf of a given user - for more information about autofill, read
@@ -575,6 +577,20 @@
*/
public static final String SERVICE_META_DATA = "android.autofill";
+ /**
+ * Name of the {@link IResultReceiver} extra used to return the primary result of a request.
+ *
+ * @hide
+ */
+ public static final String EXTRA_RESULT = "result";
+
+ /**
+ * Name of the {@link IResultReceiver} extra used to return the error reason of a request.
+ *
+ * @hide
+ */
+ public static final String EXTRA_ERROR = "error";
+
private final IAutoFillService mInterface = new IAutoFillService.Stub() {
@Override
public void onConnectedStateChanged(boolean connected) {
@@ -603,6 +619,14 @@
AutofillService::onSaveRequest,
AutofillService.this, request, new SaveCallback(callback)));
}
+
+ @Override
+ public void onSavedPasswordCountRequest(IResultReceiver receiver) {
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onSavedDatasetsInfoRequest,
+ AutofillService.this,
+ new SavedDatasetsInfoCallbackImpl(receiver, SavedDatasetsInfo.TYPE_PASSWORDS)));
+ }
};
private Handler mHandler;
@@ -673,6 +697,19 @@
@NonNull SaveCallback callback);
/**
+ * Called from system settings to display information about the datasets the user saved to this
+ * service.
+ *
+ * <p>There is no timeout for the request, but it's recommended to return the result within a
+ * few seconds, or the user may navigate away from the activity that would display the result.
+ *
+ * @param callback callback for responding to the request
+ */
+ public void onSavedDatasetsInfoRequest(@NonNull SavedDatasetsInfoCallback callback) {
+ callback.onError(SavedDatasetsInfoCallback.ERROR_UNSUPPORTED);
+ }
+
+ /**
* Called when the Android system disconnects from the service.
*
* <p> At this point this service may no longer be an active {@link AutofillService}.
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index 23a1a3f..d88e094 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -31,4 +31,5 @@
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
+ void onSavedPasswordCountRequest(in IResultReceiver receiver);
}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfo.java b/core/java/android/service/autofill/SavedDatasetsInfo.java
new file mode 100644
index 0000000..6a4d2b8
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfo.java
@@ -0,0 +1,186 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.StringDef;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A result returned from
+ * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstDefs = true,
+ genEqualsHashCode = true)
+public final class SavedDatasetsInfo {
+
+ /**
+ * Any other type of datasets.
+ */
+ public static final String TYPE_OTHER = "other";
+
+ /**
+ * Datasets such as login credentials.
+ */
+ public static final String TYPE_PASSWORDS = "passwords";
+
+ /**
+ * The type of the datasets that this info is about.
+ */
+ @NonNull
+ @Type
+ private final String mType;
+
+ /**
+ * The number of datasets of {@link #getType() this type} that the user has saved to the
+ * service.
+ */
+ @IntRange(from = 0)
+ private final int mCount;
+
+
+ // Code below generated by codegen v1.0.22.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @StringDef(prefix = "TYPE_", value = {
+ TYPE_OTHER,
+ TYPE_PASSWORDS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Type {}
+
+ /**
+ * Creates a new SavedDatasetsInfo.
+ *
+ * @param type
+ * The type of the datasets.
+ * @param count
+ * The number of datasets of this type that the user has saved to the service.
+ */
+ @DataClass.Generated.Member
+ public SavedDatasetsInfo(
+ @NonNull @Type String type,
+ @IntRange(from = 0) int count) {
+ this.mType = type;
+
+ if (!(java.util.Objects.equals(mType, TYPE_OTHER))
+ && !(java.util.Objects.equals(mType, TYPE_PASSWORDS))) {
+ throw new java.lang.IllegalArgumentException(
+ "type was " + mType + " but must be one of: "
+ + "TYPE_OTHER(" + TYPE_OTHER + "), "
+ + "TYPE_PASSWORDS(" + TYPE_PASSWORDS + ")");
+ }
+
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mType);
+ this.mCount = count;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mCount,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The type of the datasets.
+ */
+ @DataClass.Generated.Member
+ public @NonNull @Type String getType() {
+ return mType;
+ }
+
+ /**
+ * The number of datasets of this type that the user has saved to the service.
+ */
+ @DataClass.Generated.Member
+ public @IntRange(from = 0) int getCount() {
+ return mCount;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "SavedDatasetsInfo { " +
+ "type = " + mType + ", " +
+ "count = " + mCount +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(SavedDatasetsInfo other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ SavedDatasetsInfo that = (SavedDatasetsInfo) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mType, that.mType)
+ && mCount == that.mCount;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mType);
+ _hash = 31 * _hash + mCount;
+ return _hash;
+ }
+
+ @DataClass.Generated(
+ time = 1615325704446L,
+ codegenVersion = "1.0.22",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/SavedDatasetsInfo.java",
+ inputSignatures = "public static final java.lang.String TYPE_OTHER\npublic static final java.lang.String TYPE_PASSWORDS\nprivate final @android.annotation.NonNull @android.service.autofill.SavedDatasetsInfo.Type java.lang.String mType\nprivate final @android.annotation.IntRange int mCount\nclass SavedDatasetsInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallback.java b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java
new file mode 100644
index 0000000..a47105a
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfoCallback.java
@@ -0,0 +1,72 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Set;
+
+/**
+ * Handles the response to
+ * {@link AutofillService#onSavedDatasetsInfoRequest(SavedDatasetsInfoCallback)}.
+ * <p>
+ * Use {@link #onSuccess(Set)} to return the computed info about the datasets the user saved to this
+ * service. If there was an error querying the info, or if the service is unable to do so at this
+ * time (for example, if the user isn't logged in), call {@link #onError(int)}.
+ * <p>
+ * This callback can be used only once.
+ */
+public interface SavedDatasetsInfoCallback {
+
+ /** @hide */
+ @IntDef(prefix = {"ERROR_"}, value = {
+ ERROR_OTHER,
+ ERROR_UNSUPPORTED,
+ ERROR_NEEDS_USER_ACTION
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Error {
+ }
+
+ /**
+ * The result could not be computed for any other reason.
+ */
+ int ERROR_OTHER = 0;
+ /**
+ * The service does not support this request.
+ */
+ int ERROR_UNSUPPORTED = 1;
+ /**
+ * The result cannot be computed until the user takes some action, such as setting up their
+ * account.
+ */
+ int ERROR_NEEDS_USER_ACTION = 2;
+
+ /**
+ * Successfully respond to the request with the info on each type of saved datasets.
+ */
+ void onSuccess(@NonNull Set<SavedDatasetsInfo> results);
+
+ /**
+ * Respond to the request with an error. System settings may display a suitable notice to the
+ * user.
+ */
+ void onError(@Error int error);
+}
diff --git a/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.java
new file mode 100644
index 0000000..b8a8cde
--- /dev/null
+++ b/core/java/android/service/autofill/SavedDatasetsInfoCallbackImpl.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 android.service.autofill;
+
+import static android.service.autofill.AutofillService.EXTRA_ERROR;
+import static android.service.autofill.AutofillService.EXTRA_RESULT;
+
+import static com.android.internal.util.Preconditions.checkArgumentInRange;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.os.IResultReceiver;
+
+import java.util.Set;
+
+final class SavedDatasetsInfoCallbackImpl implements SavedDatasetsInfoCallback {
+ private static final String TAG = "AutofillService";
+
+ @NonNull
+ private final IResultReceiver mReceiver;
+ @NonNull
+ private final String mType;
+
+ /**
+ * Creates a {@link SavedDatasetsInfoCallback} that returns the {@link
+ * SavedDatasetsInfo#getCount() number} of saved datasets of {@code type} to the {@code
+ * receiver}.
+ */
+ SavedDatasetsInfoCallbackImpl(@NonNull IResultReceiver receiver, @NonNull String type) {
+ mReceiver = requireNonNull(receiver);
+ mType = requireNonNull(type);
+ }
+
+ @Override
+ public void onSuccess(@NonNull Set<SavedDatasetsInfo> results) {
+ requireNonNull(results);
+ if (results.isEmpty()) {
+ send(1, null);
+ return;
+ }
+ int count = -1;
+ for (SavedDatasetsInfo info : results) {
+ if (mType.equals(info.getType())) {
+ count = info.getCount();
+ }
+ }
+ if (count < 0) {
+ send(1, null);
+ return;
+ }
+ Bundle bundle = new Bundle(/* capacity= */ 1);
+ bundle.putInt(EXTRA_RESULT, count);
+ send(0, bundle);
+ }
+
+ @Override
+ public void onError(@Error int error) {
+ checkArgumentInRange(error, ERROR_OTHER, ERROR_NEEDS_USER_ACTION, "error");
+ Bundle bundle = new Bundle(/* capacity= */ 1);
+ bundle.putInt(EXTRA_ERROR, error);
+ send(1, bundle);
+ }
+
+ private void send(int resultCode, Bundle bundle) {
+ try {
+ mReceiver.send(resultCode, bundle);
+ } catch (DeadObjectException e) {
+ Log.w(TAG, "Failed to send onSavedPasswordCountRequest result: " + e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 919c6e5..913ceae 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -71,7 +71,7 @@
DEFAULT_FLAGS.put("settings_silky_home", "true");
DEFAULT_FLAGS.put("settings_contextual_home", "false");
DEFAULT_FLAGS.put(SETTINGS_PROVIDER_MODEL, "false");
- DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "false");
+ DEFAULT_FLAGS.put(SETTINGS_USE_NEW_BACKUP_ELIGIBILITY_RULES, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SECURITY_HUB, "false");
}
diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java
index b0916d3..f2bc0c5 100644
--- a/core/java/android/util/SparseLongArray.java
+++ b/core/java/android/util/SparseLongArray.java
@@ -164,7 +164,7 @@
}
/**
- * Returns the number of key-value mappings that this SparseIntArray
+ * Returns the number of key-value mappings that this SparseLongArray
* currently stores.
*/
public int size() {
@@ -246,7 +246,7 @@
}
/**
- * Removes all key-value mappings from this SparseIntArray.
+ * Removes all key-value mappings from this SparseLongArray.
*/
public void clear() {
mSize = 0;
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/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 870fd8c..11b161a 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -79,6 +79,26 @@
mInputToken = inputToken;
}
+ /**
+ * Constructs a copy of {@code SurfacePackage} with an independent lifetime.
+ *
+ * The caller can use this to create an independent copy in situations where ownership of
+ * the {@code SurfacePackage} would be transferred elsewhere, such as attaching to a
+ * {@code SurfaceView}, returning as {@code Binder} result value, etc. The caller is
+ * responsible for releasing this copy when its done.
+ *
+ * @param other {@code SurfacePackage} to create a copy of.
+ */
+ public SurfacePackage(@NonNull SurfacePackage other) {
+ SurfaceControl otherSurfaceControl = other.mSurfaceControl;
+ if (otherSurfaceControl != null && otherSurfaceControl.isValid()) {
+ mSurfaceControl = new SurfaceControl();
+ mSurfaceControl.copyFrom(otherSurfaceControl, "SurfacePackage");
+ }
+ mAccessibilityEmbeddedConnection = other.mAccessibilityEmbeddedConnection;
+ mInputToken = other.mInputToken;
+ }
+
private SurfacePackage(Parcel in) {
mSurfaceControl = new SurfaceControl();
mSurfaceControl.readFromParcel(in);
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index f1eef9f..82106b0 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -1389,6 +1389,8 @@
// If we are using BLAST, merge the transaction with the viewroot buffer transaction.
viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
return;
+ } else {
+ mRtTransaction.apply();
}
}
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 61%
rename from core/java/android/app/WindowContext.java
rename to core/java/android/window/WindowContext.java
index d44918c..375f4cf 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;
@@ -27,11 +27,7 @@
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.Display;
-import android.view.IWindowManager;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,11 +45,11 @@
@UiContext
public class WindowContext extends ContextWrapper {
private final WindowManager mWindowManager;
- private final IWindowManager mWms;
- private final WindowTokenClient mToken;
- private boolean mListenerRegistered;
+ private final @WindowManager.LayoutParams.WindowType int mType;
+ private final @Nullable Bundle mOptions;
private final ComponentCallbacksController mCallbacksController =
new ComponentCallbacksController();
+ private final WindowContextController mController;
/**
* Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -64,47 +60,23 @@
* @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 */);
-
- mWms = WindowManagerGlobal.getWindowManagerService();
- mToken = new WindowTokenClient();
-
- final ContextImpl contextImpl = createBaseWindowContext(base, mToken, display);
- attachBaseContext(contextImpl);
- contextImpl.setOuterContext(this);
-
- mToken.attachContext(this);
-
+ mType = type;
+ mOptions = options;
mWindowManager = createWindowContextWindowManager(this);
+ IBinder token = getWindowContextToken();
+ mController = new WindowContextController(token);
- 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() {
+ mController.registerListener(mType, getDisplayId(), mOptions);
}
@Override
@@ -124,21 +96,15 @@
/** Used for test to invoke because we can't invoke finalize directly. */
@VisibleForTesting
public void release() {
- if (mListenerRegistered) {
- mListenerRegistered = false;
- try {
- mWms.unregisterWindowContextListener(mToken);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
+ mController.unregisterListenerIfNeeded();
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/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
new file mode 100644
index 0000000..6143414
--- /dev/null
+++ b/core/java/android/window/WindowContextController.java
@@ -0,0 +1,96 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.IWindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * The controller to manage {@link WindowContext} listener, such as registering and unregistering
+ * the listener.
+ *
+ * @hide
+ */
+public class WindowContextController {
+ private final IWindowManager mWms;
+ @VisibleForTesting
+ public boolean mListenerRegistered;
+ @NonNull
+ private final IBinder mToken;
+
+ /**
+ * Window Context Controller constructor
+ *
+ * @param token The token to register to the window context listener. It is usually from
+ * {@link Context#getWindowContextToken()}.
+ */
+ public WindowContextController(@NonNull IBinder token) {
+ mToken = token;
+ mWms = WindowManagerGlobal.getWindowManagerService();
+ }
+
+ /** Used for test only. DO NOT USE it in production code. */
+ @VisibleForTesting
+ public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+ mToken = token;
+ mWms = mockWms;
+ }
+
+ /**
+ * Registers the {@code mToken} to the window context listener.
+ *
+ * @param type The window type of the {@link WindowContext}
+ * @param displayId The {@link Context#getDisplayId() ID of display} to associate with
+ * @param options The window context launched option
+ */
+ public void registerListener(@WindowType int type, int displayId, @Nullable Bundle options) {
+ if (mListenerRegistered) {
+ throw new UnsupportedOperationException("A Window Context can only register a listener"
+ + " once.");
+ }
+ try {
+ mListenerRegistered = mWms.registerWindowContextListener(mToken, type, displayId,
+ options);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Unregisters the window context listener associated with the {@code mToken} if it has been
+ * registered.
+ */
+ public void unregisterListenerIfNeeded() {
+ if (mListenerRegistered) {
+ try {
+ mWms.unregisterWindowContextListener(mToken);
+ mListenerRegistered = false;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+}
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/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index dd837fc..922f96e 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -194,12 +194,12 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_work_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_work_apps_explanation);
}
}
@@ -209,12 +209,12 @@
if (mIsSendAction) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_share_with_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_share_with_personal_apps_explanation);
} else {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
}
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 2464fc7..a2f014c 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -205,7 +205,7 @@
protected void showNoPersonalToWorkIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_work_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_work_apps_explanation);
}
@@ -213,7 +213,7 @@
protected void showNoWorkToPersonalIntentsEmptyState(ResolverListAdapter activeListAdapter) {
showEmptyState(activeListAdapter,
R.drawable.ic_sharing_disabled,
- R.string.resolver_cant_access_personal_apps,
+ R.string.resolver_cross_profile_blocked,
R.string.resolver_cant_access_personal_apps_explanation);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index ec0a8d8..27e77bf 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -16525,6 +16525,8 @@
// Pull the clock time. This may update the time and make a new history entry
// if we had originally pulled a time before the RTC was set.
getStartClockTime();
+
+ updateSystemServiceCallStats();
}
public void dumpLocked(Context context, PrintWriter pw, int flags, int reqUid, long histStart) {
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 4f99c94..f8ae0c1 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -21,9 +21,7 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Bundle;
import android.os.UidBatteryConsumer;
-import android.os.UserHandle;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
@@ -88,29 +86,31 @@
}
/**
+ * Returns true if the last update was too long ago for the tolerances specified
+ * by the supplied queries.
+ */
+ public boolean shouldUpdateStats(List<BatteryUsageStatsQuery> queries,
+ long lastUpdateTimeStampMs) {
+ long allowableStatsAge = Long.MAX_VALUE;
+ for (int i = queries.size() - 1; i >= 0; i--) {
+ BatteryUsageStatsQuery query = queries.get(i);
+ allowableStatsAge = Math.min(allowableStatsAge, query.getMaxStatsAge());
+ }
+
+ return mStats.mClocks.elapsedRealtime() - lastUpdateTimeStampMs > allowableStatsAge;
+ }
+
+ /**
* Returns snapshots of battery attribution data, one per supplied query.
*/
public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
-
- // TODO(b/174186345): instead of BatteryStatsHelper, use PowerCalculators directly.
- final BatteryStatsHelper batteryStatsHelper = new BatteryStatsHelper(mContext,
- false /* collectBatteryBroadcast */);
- batteryStatsHelper.create((Bundle) null);
- final List<UserHandle> users = new ArrayList<>();
- for (int i = 0; i < queries.size(); i++) {
- BatteryUsageStatsQuery query = queries.get(i);
- for (int userId : query.getUserIds()) {
- UserHandle userHandle = UserHandle.of(userId);
- if (!users.contains(userHandle)) {
- users.add(userHandle);
- }
- }
- }
- batteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, users);
-
ArrayList<BatteryUsageStats> results = new ArrayList<>(queries.size());
- for (int i = 0; i < queries.size(); i++) {
- results.add(getBatteryUsageStats(queries.get(i)));
+ synchronized (mStats) {
+ mStats.prepareForDumpLocked();
+
+ for (int i = 0; i < queries.size(); i++) {
+ results.add(getBatteryUsageStats(queries.get(i)));
+ }
}
return results;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8a47135..1d462bc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1230,7 +1230,7 @@
android:description="@string/permdesc_answerPhoneCalls"
android:protectionLevel="dangerous|runtime" />
- <!-- Allows a calling application which manages it own calls through the self-managed
+ <!-- Allows a calling application which manages its own calls through the self-managed
{@link android.telecom.ConnectionService} APIs. See
{@link android.telecom.PhoneAccount#CAPABILITY_SELF_MANAGED} for more information on the
self-managed ConnectionService APIs.
@@ -4440,6 +4440,13 @@
<permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to disable system sound effects when the user exits one of
+ the application's activities.
+ <p>Not for use by third-party applications.</p>
+ @hide -->
+ <permission android:name="android.permission.DISABLE_SYSTEM_SOUND_EFFECTS"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to provide remote displays.
<p>Not for use by third-party applications.</p>
@hide -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 140163e..dc4f52e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1853,6 +1853,25 @@
-->
<attr name="preserveLegacyExternalStorage" format="boolean" />
+ <!-- If {@code true} this app would like optimized external storage access.
+
+ <p> This flag can only be used by apps holding
+ <ul>
+ <li>{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission or
+ <li>{@link android.app.role}#SYSTEM_GALLERY role.
+ </ul>
+ When the flag is set, bulk file path operations will be optimized.
+
+ The default value is {@code true} if
+ <ul>
+ <li>app has {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE} permission and
+ targets targetSDK<=30.
+ <li>app has {@link android.app.role}#SYSTEM_GALLERY role and targetSDK<=29
+ </ul>
+ {@code false} otherwise.
+ -->
+ <attr name="requestOptimizedExternalStorageAccess" format="boolean" />
+
<!-- If {@code true} this app declares that it should be visible to all other apps on
device, regardless of what they declare via the {@code queries} tags in their
manifest.
@@ -2843,6 +2862,13 @@
{@link android.content.Context#sendBroadcast(Intent, String)} being used.
Multiple tags can be specified separated by '|'. -->
<attr name="attributionTags"/>
+ <!-- Specifies whether a home sound effect should be played if the home app moves to
+ front after an activity with this flag set to <code>true</code>.
+ <p>The default value of this attribute is <code>true</code>.
+ <p>Also note that home sounds are only played if the device supports home sounds,
+ usually TVs.
+ <p>Requires permission {@code android.permission.DISABLE_SYSTEM_SOUND_EFFECTS}. -->
+ <attr name="playHomeTransitionSound" format="boolean"/>
</declare-styleable>
<!-- The <code>activity-alias</code> tag declares a new
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 7694faf..16feb4f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3092,6 +3092,9 @@
<public name="attributionTags"/>
<public name="suppressesSpellChecker" />
<public name="usesPermissionFlags" />
+ <public name="requestOptimizedExternalStorageAccess" />
+ <!-- @hide @SystemApi -->
+ <public name="playHomeTransitionSound" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0b3c405..c63a02b 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2815,6 +2815,15 @@
<!-- Displayed to the user to inform them that an app has accessed clipboard data (pasted as in "copy and paste") [CHAR LIMIT=50] -->
<string name="pasted_from_clipboard"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted from clipboard</string>
+ <!-- Displayed to the user to inform them that an app has accessed text from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_text"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted text you copied</string>
+
+ <!-- Displayed to the user to inform them that an app has accessed an image from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_image"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted an image you copied</string>
+
+ <!-- Displayed to the user to inform them that an app has accessed content from clipboard (pasted as in "copy and paste") [CHAR LIMIT=50] -->
+ <string name="pasted_content"><xliff:g id="pasting_app_name" example="Gmail">%1$s</xliff:g> pasted content you copied</string>
+
<!-- Menu item displayed at the end of a menu to allow users to see another page worth of menu items. This is shown on any app's menu as long as the app has too many items in the menu.-->
<string name="more_item_label">More</string>
<!-- Prepended to the shortcut for a menu item to indicate that the user should hold the MENU button together with the shortcut to invoke the item. For example, if the shortcut to open a new tab in browser is MENU and B together, then this would be prepended to the letter "B" -->
@@ -5626,30 +5635,24 @@
<!-- Accessibility label for the work tab button. [CHAR LIMIT=NONE] -->
<string name="resolver_work_tab_accessibility">Work view</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with work apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps">Can\u2019t share this with work apps</string>
+ <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this content across profiles. [CHAR LIMIT=NONE] -->
+ <string name="resolver_cross_profile_blocked">Blocked by your IT admin</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow this specific content to be shared with apps in the work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_work_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your work profile</string>
+ <string name="resolver_cant_share_with_work_apps_explanation">This content can\u2019t be shared with work apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a work app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps">Can\u2019t open this with work apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their work profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_work_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your work profile</string>
+ <string name="resolver_cant_access_work_apps_explanation">This content can\u2019t be opened with work apps</string>
- <!-- Title of a screen. This text lets the user know that their IT admin doesn't allow them to share this specific content with personal apps. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps">Can\u2019t share this with personal apps</string>
<!-- Error message. This text is explaining that the user's IT admin doesn't allow them to share this specific content with apps in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_share_with_personal_apps_explanation">Your IT admin doesn\u2019t allow you to share this content with apps in your personal profile</string>
+ <string name="resolver_cant_share_with_personal_apps_explanation">This content can\u2019t be shared with personal apps</string>
- <!-- Title of an error screen. This error message lets the user know that their IT admin doesn't allow them to open this specific content with a personal app. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps">Can\u2019t open this with personal apps</string>
<!-- Error message. This message lets the user know that their IT admin doesn't allow them to open this specific content with an app in their personal profile. [CHAR LIMIT=NONE] -->
- <string name="resolver_cant_access_personal_apps_explanation">Your IT admin doesn\u2019t allow you to open this content with apps in your personal profile</string>
+ <string name="resolver_cant_access_personal_apps_explanation">This content can\u2019t be opened with personal apps</string>
<!-- Error message. This text lets the user know that they need to turn on work apps in order to share or open content. There's also a button a user can tap to turn on the apps. [CHAR LIMIT=NONE] -->
<string name="resolver_turn_on_work_apps">Work profile is paused</string>
<!-- Button text. This button turns on a user's work profile so they can access their work apps and data. [CHAR LIMIT=NONE] -->
- <string name="resolver_switch_on_work">Turn on</string>
+ <string name="resolver_switch_on_work">Tap to turn on</string>
<!-- Error message. This text lets the user know that their current work apps don't support the specific content that they're trying to share. [CHAR LIMIT=NONE] -->
<string name="resolver_no_work_apps_available_share">No work apps can support this content</string>
@@ -5661,6 +5664,15 @@
<!-- Error message. This text lets the user know that their current personal apps can't open this specific content. [CHAR LIMIT=NONE] -->
<string name="resolver_no_personal_apps_available_resolve">No personal apps can open this content</string>
+ <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_open_in_personal">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in personal profile?</string>
+ <!-- Dialog title. User must choose between opening content in a cross-profile app or same-profile browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_open_in_work">Open in <xliff:g id="app" example="YouTube">%s</xliff:g> in work profile?</string>
+ <!-- Button option. Open the link in the personal browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_use_personal_browser">Use personal browser</string>
+ <!-- Button option. Open the link in the work browser. [CHAR LIMIT=NONE] -->
+ <string name="miniresolver_use_work_browser">Use work browser</string>
+
<!-- Icc depersonalization related strings -->
<!-- Label text for PIN entry widget on SIM Network Depersonalization panel [CHAR LIMIT=none] -->
<string name="PERSOSUBSTATE_SIM_NETWORK_ENTRY">SIM network unlock PIN</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a704936..01374b1 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -555,6 +555,9 @@
<java-symbol type="string" name="paste_as_plain_text" />
<java-symbol type="string" name="pasted_from_app" />
<java-symbol type="string" name="pasted_from_clipboard" />
+ <java-symbol type="string" name="pasted_text" />
+ <java-symbol type="string" name="pasted_image" />
+ <java-symbol type="string" name="pasted_content" />
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
<java-symbol type="string" name="redo" />
@@ -4092,13 +4095,10 @@
<java-symbol type="id" name="resolver_empty_state_container" />
<java-symbol type="id" name="button_bar_container" />
<java-symbol type="id" name="title_container" />
- <java-symbol type="string" name="resolver_cant_share_with_work_apps" />
+ <java-symbol type="string" name="resolver_cross_profile_blocked" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
<java-symbol type="string" name="resolver_cant_share_with_personal_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_work_apps" />
<java-symbol type="string" name="resolver_cant_access_work_apps_explanation" />
- <java-symbol type="string" name="resolver_cant_access_personal_apps" />
<java-symbol type="string" name="resolver_cant_access_personal_apps_explanation" />
<java-symbol type="string" name="resolver_turn_on_work_apps" />
<java-symbol type="string" name="resolver_no_work_apps_available_share" />
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 7ef1d5e..6d9e2ea5 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -51,7 +51,7 @@
CompletableFuture<AppSearchResult<SetSchemaResponse>> schemaFuture =
new CompletableFuture<>();
mSearchSession.setSchema(
- new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor,
+ new SetSchemaRequest.Builder().setForceOverride(true).build(), mExecutor, mExecutor,
schemaFuture::complete);
schemaFuture.get().getResultValue();
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index c61a4b5..0b94589 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -2,3 +2,5 @@
per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file ComponentCallbacksControllerTest = file:/services/core/java/com/android/server/wm/OWNERS
+per-file ComponentCallbacksControllerTest = charlesccchen@google.com
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/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
new file mode 100644
index 0000000..e4fc19c
--- /dev/null
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.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 android.window;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.IWindowManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContextController}
+ *
+ * <p>Build/Install/Run:
+ * atest FrameworksCoreTests:WindowContextControllerTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextControllerTest {
+ private WindowContextController mController;
+ private IWindowManager mMockWms;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockWms = mock(IWindowManager.class);
+ mController = new WindowContextController(new Binder(), mMockWms);
+
+ doReturn(true).when(mMockWms).registerWindowContextListener(
+ any(), anyInt(), anyInt(), any());
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testRegisterListenerTwiceThrowException() {
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+ }
+
+ @Test
+ public void testUnregisterListenerIfNeeded_NotRegisteredYet_DoNothing() throws Exception {
+ mController.unregisterListenerIfNeeded();
+
+ verify(mMockWms, never()).registerWindowContextListener(any(), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void testRegisterAndUnRegisterListener() {
+ mController.registerListener(TYPE_APPLICATION_OVERLAY, DEFAULT_DISPLAY,
+ null /* options */);
+
+ assertThat(mController.mListenerRegistered).isTrue();
+
+ mController.unregisterListenerIfNeeded();
+
+ assertThat(mController.mListenerRegistered).isFalse();
+ }
+}
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/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 4c58ad3..80d47a9 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1608,14 +1608,13 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_share_with_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@Test
public void testWorkTab_workProfileDisabled_emptyStateShown() {
// enable the work tab feature flag
- ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
int workProfileTargets = 4;
List<ResolvedComponentInfo> personalResolvedComponentInfos =
@@ -1627,6 +1626,7 @@
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
final ChooserWrapperActivity activity =
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
waitForIdle();
@@ -1686,7 +1686,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cant_share_with_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@@ -2116,7 +2116,7 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 7dc5a8b..68287ca 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -650,7 +650,7 @@
onView(withId(R.id.contentPanel))
.perform(swipeUp());
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
@@ -726,7 +726,7 @@
onView(withText(R.string.resolver_work_tab)).perform(click());
waitForIdle();
- onView(withText(R.string.resolver_cant_access_work_apps))
+ onView(withText(R.string.resolver_cross_profile_blocked))
.check(matches(isDisplayed()));
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0f59143..d36f06a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -158,4 +158,22 @@
assertThat(item.time).isEqualTo(elapsedTimeMs);
}
+
+ @Test
+ public void shouldUpdateStats() {
+ Context context = InstrumentationRegistry.getContext();
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
+ mStatsRule.getBatteryStats());
+
+ final List<BatteryUsageStatsQuery> queries = List.of(
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(1000).build(),
+ new BatteryUsageStatsQuery.Builder().setMaxStatsAgeMs(2000).build()
+ );
+
+ mStatsRule.setTime(10500, 0);
+ assertThat(provider.shouldUpdateStats(queries, 10000)).isFalse();
+
+ mStatsRule.setTime(11500, 0);
+ assertThat(provider.shouldUpdateStats(queries, 10000)).isTrue();
+ }
}
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 24d73ef..3616a4d 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -276,7 +276,11 @@
*/
@Nullable
public float[] getCornerRadii() {
- return mGradientState.mRadiusArray.clone();
+ float[] radii = mGradientState.mRadiusArray;
+ if (radii == null) {
+ return null;
+ }
+ return radii.clone();
}
/**
diff --git a/graphics/java/android/graphics/drawable/RippleShader.java b/graphics/java/android/graphics/drawable/RippleShader.java
index 5dd250c..98b9584 100644
--- a/graphics/java/android/graphics/drawable/RippleShader.java
+++ b/graphics/java/android/graphics/drawable/RippleShader.java
@@ -60,17 +60,13 @@
+ " float d = distance(uv, xy);\n"
+ " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n"
+ "}\n"
- + "\n"
- + "float getRingMask(vec2 frag, vec2 center, float r, float progress) {\n"
- + " float dist = distance(frag, center);\n"
- + " float expansion = r * .6;\n"
- + " r = r * min(1.,progress);\n"
- + " float minD = max(r - expansion, 0.);\n"
- + " float maxD = r + expansion;\n"
- + " if (dist > maxD || dist < minD) return .0;\n"
- + " return min(maxD - dist, dist - minD) / expansion; \n"
+ + "float softRing(vec2 uv, vec2 xy, float radius, float progress, float blur) {\n"
+ + " float thickness = 0.2 * radius;\n"
+ + " float currentRadius = radius * progress;\n"
+ + " float circle_outer = softCircle(uv, xy, currentRadius + thickness, blur);\n"
+ + " float circle_inner = softCircle(uv, xy, currentRadius - thickness, blur);\n"
+ + " return clamp(circle_outer - circle_inner, 0., 1.);\n"
+ "}\n"
- + "\n"
+ "float subProgress(float start, float end, float progress) {\n"
+ " float sub = clamp(progress, start, end);\n"
+ " return (sub - start) / (end - start); \n"
@@ -80,8 +76,8 @@
+ " float fadeOutNoise = subProgress(0.375, 1., in_progress);\n"
+ " float fadeOutRipple = subProgress(0.375, 0.75, in_progress);\n"
+ " vec2 center = mix(in_touch, in_origin, fadeIn);\n"
- + " float ring = getRingMask(p, center, in_maxRadius, fadeIn);\n"
- + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ + " float ring = softRing(p, center, in_maxRadius, fadeIn, 0.45);\n"
+ + " float alpha = 1. - fadeOutNoise;\n"
+ " vec2 uv = p * in_resolutionScale;\n"
+ " vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
+ " float sparkle = sparkles(densityUv, in_noisePhase) * ring * alpha;\n"
@@ -137,8 +133,7 @@
}
public void setResolution(float w, float h, int density) {
- float noiseScale = 0.8f;
- float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f * noiseScale;
+ float densityScale = density * DisplayMetrics.DENSITY_DEFAULT_SCALE * 0.5f;
setUniform("in_resolutionScale", new float[] {1f / w, 1f / h});
setUniform("in_noiseScale", new float[] {densityScale / w, densityScale / h});
}
diff --git a/keystore/java/android/security/AndroidKeyStoreMaintenance.java b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
index 35b1c16..72cea0c 100644
--- a/keystore/java/android/security/AndroidKeyStoreMaintenance.java
+++ b/keystore/java/android/security/AndroidKeyStoreMaintenance.java
@@ -139,4 +139,18 @@
return SYSTEM_ERROR;
}
}
+
+ /**
+ * Informs Keystore 2.0 that an off body event was detected.
+ */
+ public static void onDeviceOffBody() {
+ if (!android.security.keystore2.AndroidKeyStoreProvider.isInstalled()) return;
+ try {
+ getService().onDeviceOffBody();
+ } catch (Exception e) {
+ // TODO This fails open. This is not a regression with respect to keystore1 but it
+ // should get fixed.
+ Log.e(TAG, "Error while reporting device off body event.", e);
+ }
+ }
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index a08f390..b05149e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -1204,6 +1204,7 @@
* Notify keystore that the device went off-body.
*/
public void onDeviceOffBody() {
+ AndroidKeyStoreMaintenance.onDeviceOffBody();
try {
mBinder.onDeviceOffBody();
} catch (RemoteException e) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 3708e15..34c66a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -16,6 +16,8 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+
import android.annotation.UiContext;
import android.app.ResourcesManager;
import android.content.Context;
@@ -34,6 +36,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -44,14 +48,14 @@
private static final String TAG = RootTaskDisplayAreaOrganizer.class.getSimpleName();
- // Display area info. mapped by displayIds.
+ /** {@link DisplayAreaInfo} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaInfo> mDisplayAreasInfo = new SparseArray<>();
- // Display area leashes. mapped by displayIds.
+ /** Display area leashes, which is mapped by display IDs. */
private final SparseArray<SurfaceControl> mLeashes = new SparseArray<>();
private final SparseArray<ArrayList<RootTaskDisplayAreaListener>> mListeners =
new SparseArray<>();
-
+ /** {@link DisplayAreaContext} list, which is mapped by display IDs. */
private final SparseArray<DisplayAreaContext> mDisplayAreaContexts = new SparseArray<>();
private final Context mContext;
@@ -173,8 +177,9 @@
final Display display = mContext.getSystemService(DisplayManager.class)
.getDisplay(displayId);
if (display == null) {
- throw new UnsupportedOperationException("The display #" + displayId + " is invalid."
- + "displayAreaInfo:" + displayAreaInfo);
+ ProtoLog.w(WM_SHELL_TASK_ORG, "The display#%d has been removed."
+ + " Skip following steps", displayId);
+ return;
}
DisplayAreaContext daContext = mDisplayAreaContexts.get(displayId);
if (daContext == null) {
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index ade63e5..5d9fad5 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -45,7 +45,8 @@
return SkColorSpace::MakeSRGB();
}
-ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
+ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker,
+ SkCodec::ZeroInitialized zeroInit)
: mCodec(std::move(codec))
, mPeeker(std::move(peeker))
, mDecodeSize(mCodec->codec()->dimensions())
@@ -57,6 +58,7 @@
mTargetSize = swapWidthHeight() ? SkISize { mDecodeSize.height(), mDecodeSize.width() }
: mDecodeSize;
this->rewind();
+ mOptions.fZeroInitialized = zeroInit;
}
ImageDecoder::~ImageDecoder() = default;
@@ -446,10 +448,17 @@
ALOGE("Failed to invert matrix!");
}
}
+
+ // Even if the client did not provide zero initialized memory, the
+ // memory we decode into is.
+ mOptions.fZeroInitialized = SkCodec::kYes_ZeroInitialized;
}
auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &mOptions);
+ // The next call to decode() may not provide zero initialized memory.
+ mOptions.fZeroInitialized = SkCodec::kNo_ZeroInitialized;
+
if (scale || handleOrigin || mCropRect) {
SkBitmap scaledBm;
if (!scaledBm.installPixels(outputInfo, pixels, rowBytes)) {
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index cbfffd5..cef2233 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -34,8 +34,8 @@
std::unique_ptr<SkAndroidCodec> mCodec;
sk_sp<SkPngChunkReader> mPeeker;
- ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
- sk_sp<SkPngChunkReader> peeker = nullptr);
+ ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker = nullptr,
+ SkCodec::ZeroInitialized zeroInit = SkCodec::kNo_ZeroInitialized);
~ImageDecoder();
SkISize getSampledDimensions(int sampleSize) const;
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index ad7741b..f7b8c01 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -141,7 +141,8 @@
}
const bool isNinePatch = peeker->mPatch != nullptr;
- ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker));
+ ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker),
+ SkCodec::kYes_ZeroInitialized);
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
reinterpret_cast<jlong>(decoder), decoder->width(), decoder->height(),
animated, isNinePatch);
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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f957a73..9aeef07 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -568,6 +568,42 @@
public static final int FLAG_FROM_KEY = 1 << 12;
/** @hide */
+ @IntDef(prefix = {"ENCODED_SURROUND_OUTPUT_"}, value = {
+ ENCODED_SURROUND_OUTPUT_AUTO,
+ ENCODED_SURROUND_OUTPUT_NEVER,
+ ENCODED_SURROUND_OUTPUT_ALWAYS,
+ ENCODED_SURROUND_OUTPUT_MANUAL
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodedSurroundOutputMode {}
+
+ /**
+ * The surround sound formats are available for use if they are detected. This is the default
+ * mode.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0;
+
+ /**
+ * The surround sound formats are NEVER available, even if they are detected by the hardware.
+ * Those formats will not be reported.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_NEVER = 1;
+
+ /**
+ * The surround sound formats are ALWAYS available, even if they are not detected by the
+ * hardware. Those formats will be reported as part of the HDMI output capability.
+ * Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2;
+
+ /**
+ * Surround sound formats are available according to the choice of user, even if they are not
+ * detected by the hardware. Those formats will be reported as part of the HDMI output
+ * capability. Applications are then free to use either PCM or encoded output.
+ */
+ public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3;
+
+ /** @hide */
@IntDef(flag = true, prefix = "FLAG", value = {
FLAG_SHOW_UI,
FLAG_ALLOW_RINGER_MODES,
@@ -6754,6 +6790,34 @@
}
/**
+ * Sets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public boolean setEncodedSurroundMode(@EncodedSurroundOutputMode int mode) {
+ try {
+ return getService().setEncodedSurroundMode(mode);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets the surround sound mode.
+ *
+ * @return true if successful, otherwise false
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public @EncodedSurroundOutputMode int getEncodedSurroundMode() {
+ try {
+ return getService().getEncodedSurroundMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* @hide
* Returns all surround formats.
* @return a map where the key is a surround format and
@@ -6771,7 +6835,6 @@
}
/**
- * @hide
* Set a certain surround format as enabled or not.
* @param audioFormat a surround format, the value is one of
* {@link AudioFormat#ENCODING_AC3}, {@link AudioFormat#ENCODING_E_AC3},
@@ -6785,10 +6848,29 @@
* @param enabled the required surround format state, true for enabled, false for disabled
* @return true if successful, otherwise false
*/
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
public boolean setSurroundFormatEnabled(
@AudioFormat.SurroundSoundEncoding int audioFormat, boolean enabled) {
- int status = AudioSystem.setSurroundFormatEnabled(audioFormat, enabled);
- return status == AudioManager.SUCCESS;
+ try {
+ return getService().setSurroundFormatEnabled(audioFormat, enabled);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Gets whether a certain surround format is enabled or not.
+ * @param audioFormat a surround format
+ *
+ * @return whether the required surround format is enabled
+ */
+ @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
+ public boolean isSurroundFormatEnabled(@AudioFormat.SurroundSoundEncoding int audioFormat) {
+ try {
+ return getService().isSurroundFormatEnabled(audioFormat);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
/**
diff --git a/media/java/android/media/AudioProfile.java b/media/java/android/media/AudioProfile.java
index 9774e80..ac96e6f 100644
--- a/media/java/android/media/AudioProfile.java
+++ b/media/java/android/media/AudioProfile.java
@@ -88,7 +88,7 @@
if (ints == null || ints.length == 0) {
return "";
}
- return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X, ", anInt))
- .collect(Collectors.joining());
+ return Arrays.stream(ints).mapToObj(anInt -> String.format("0x%02X", anInt))
+ .collect(Collectors.joining(", "));
}
}
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 0d61399..3399377 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
import android.media.MediaRecorder.Source;
import android.media.audiopolicy.AudioMix;
import android.media.audiopolicy.AudioPolicy;
+import android.media.metrics.LogSessionId;
import android.media.permission.Identity;
import android.media.projection.MediaProjection;
import android.os.Binder;
@@ -282,9 +283,9 @@
/**
* The log session id used for metrics.
- * A null or empty string here means it is not set.
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
*/
- private String mLogSessionId;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
//---------------------------------------------------------
// Constructor, Finalize
@@ -1963,24 +1964,34 @@
}
/**
- * Sets a string handle to this AudioRecord for metrics collection.
+ * Sets a {@link LogSessionId} instance to this AudioRecord for metrics collection.
*
- * @param logSessionId a string which is used to identify this object
- * to the metrics service. Proper generated Ids must be obtained
- * from the Java metrics service and should be considered opaque.
- * Use null to remove the logSessionId association.
+ * @param logSessionId a {@link LogSessionId} instance which is used to
+ * identify this object to the metrics service. Proper generated
+ * Ids must be obtained from the Java metrics service and should
+ * be considered opaque. Use
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+ * logSessionId association.
* @throws IllegalStateException if AudioRecord not initialized.
- *
- * @hide
*/
- public void setLogSessionId(@Nullable String logSessionId) {
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
if (mState == STATE_UNINITIALIZED) {
throw new IllegalStateException("AudioRecord not initialized");
}
- native_setLogSessionId(logSessionId);
+ String stringId = logSessionId.getStringId();
+ native_setLogSessionId(stringId);
mLogSessionId = logSessionId;
}
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
//---------------------------------------------------------
// Interface definitions
//--------------------
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index bccefdf..7a2b022 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -26,6 +26,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.metrics.LogSessionId;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -45,6 +46,7 @@
import java.nio.ByteOrder;
import java.nio.NioUtils;
import java.util.LinkedList;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -567,9 +569,9 @@
/**
* The log session id used for metrics.
- * A null or empty string here means it is not set.
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} here means it is not set.
*/
- private String mLogSessionId;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
//--------------------------------
// Used exclusively by native code
@@ -4044,24 +4046,35 @@
}
/**
- * Sets a string handle to this AudioTrack for metrics collection.
+ * Sets a {@link LogSessionId} instance to this AudioTrack for metrics collection.
*
- * @param logSessionId a string which is used to identify this object
- * to the metrics service. Proper generated Ids must be obtained
- * from the Java metrics service and should be considered opaque.
- * Use null to remove the logSessionId association.
+ * @param logSessionId a {@link LogSessionId} instance which is used to
+ * identify this object to the metrics service. Proper generated
+ * Ids must be obtained from the Java metrics service and should
+ * be considered opaque. Use
+ * {@link LogSessionId#LOG_SESSION_ID_NONE} to remove the
+ * logSessionId association.
* @throws IllegalStateException if AudioTrack not initialized.
*
- * @hide
*/
- public void setLogSessionId(@Nullable String logSessionId) {
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
if (mState == STATE_UNINITIALIZED) {
throw new IllegalStateException("track not initialized");
}
- native_setLogSessionId(logSessionId);
+ String stringId = logSessionId.getStringId();
+ native_setLogSessionId(stringId);
mLogSessionId = logSessionId;
}
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
//---------------------------------------------------------
// Inner classes
//--------------------
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 4f87fe6..ee945d5 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -158,6 +158,14 @@
oneway void reloadAudioSettings();
+ boolean setSurroundFormatEnabled(int audioFormat, boolean enabled);
+
+ boolean isSurroundFormatEnabled(int audioFormat);
+
+ boolean setEncodedSurroundMode(int mode);
+
+ int getEncodedSurroundMode();
+
oneway void avrcpSupportsAbsoluteVolume(String address, boolean support);
void setSpeakerphoneOn(IBinder cb, boolean on);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index a7e2b65..8db75d6 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -25,7 +25,6 @@
import android.graphics.SurfaceTexture;
import android.hardware.HardwareBuffer;
import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.metrics.PlaybackComponent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -1539,7 +1538,7 @@
</tbody>
</table>
*/
-final public class MediaCodec implements PlaybackComponent {
+final public class MediaCodec {
/**
* Per buffer metadata includes an offset and size specifying
@@ -1697,22 +1696,6 @@
private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
- /**
- * @hide
- */
- @Override
- public void setPlaybackId(@NonNull String playbackId) {
- // TODO: add a native method to pass the ID to the native code for logging.
- mPlaybackId = playbackId;
- }
- /**
- * @hide
- */
- @Override
- public String getPlaybackId() {
- return mPlaybackId;
- }
-
private class EventHandler extends Handler {
private MediaCodec mCodec;
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index ae64c02..10b99dc 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -27,7 +27,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.media.metrics.PlaybackComponent;
+import android.media.metrics.LogSessionId;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
@@ -50,6 +50,7 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@@ -1379,7 +1380,7 @@
public byte[] openSession(@SecurityLevel int level) throws
NotProvisionedException, ResourceBusyException {
byte[] sessionId = openSessionNative(level);
- mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponentImpl(sessionId));
+ mPlaybackComponentMap.put(ByteBuffer.wrap(sessionId), new PlaybackComponent(sessionId));
return sessionId;
}
@@ -2929,8 +2930,8 @@
/**
* Obtain a {@link PlaybackComponent} associated with a DRM session.
- * Call {@link PlaybackComponent#setPlaybackId(String)} on the returned object
- * to associate a playback session with the DRM session.
+ * Call {@link PlaybackComponent#setLogSessionId(LogSessionId)} on
+ * the returned object to associate a playback session with the DRM session.
*
* @param sessionId a DRM session ID obtained from {@link #openSession()}
* @return a {@link PlaybackComponent} associated with the session,
@@ -2945,28 +2946,37 @@
return mPlaybackComponentMap.get(ByteBuffer.wrap(sessionId));
}
- private native void setPlaybackId(byte[] sessionId, String playbackId);
+ private native void setPlaybackId(byte[] sessionId, String logSessionId);
- private final class PlaybackComponentImpl implements PlaybackComponent {
+ /** This class contains the Drm session ID and log session ID */
+ public final class PlaybackComponent {
private final byte[] mSessionId;
- private String mPlaybackId = "";
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
- public PlaybackComponentImpl(byte[] sessionId) {
+ /** @hide */
+ public PlaybackComponent(byte[] sessionId) {
mSessionId = sessionId;
}
- @Override
- public void setPlaybackId(@NonNull String playbackId) {
- if (playbackId == null) {
+
+ /**
+ * Gets the {@link LogSessionId}.
+ */
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ Objects.requireNonNull(logSessionId);
+ if (logSessionId.getStringId() == null) {
throw new IllegalArgumentException("playbackId is null");
}
- MediaDrm.this.setPlaybackId(mSessionId, playbackId);
- mPlaybackId = playbackId;
+ MediaDrm.this.setPlaybackId(mSessionId, logSessionId.getStringId());
+ mLogSessionId = logSessionId;
}
- @Override
- @NonNull public String getPlaybackId() {
- return mPlaybackId;
+
+ /**
+ * Returns the {@link LogSessionId}.
+ */
+ @NonNull public LogSessionId getLogSessionId() {
+ return mLogSessionId;
}
}
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 8f60330..283f1f1 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -22,6 +22,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
+import android.media.metrics.LogSessionId;
import android.net.Uri;
import android.os.IBinder;
import android.os.IHwBinder;
@@ -40,6 +41,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -73,7 +75,7 @@
* <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
* when used with network-based content.
*/
-final public class MediaExtractor {
+public final class MediaExtractor {
public MediaExtractor() {
native_setup();
}
@@ -768,6 +770,22 @@
public native boolean hasCacheReachedEndOfStream();
/**
+ * Sets the {@link LogSessionId} for MediaExtractor.
+ */
+ public void setLogSessionId(@NonNull LogSessionId logSessionId) {
+ mLogSessionId = Objects.requireNonNull(logSessionId);
+ // TODO: implement native_setPlaybackId(playbackId);
+ }
+
+ /**
+ * Returns the {@link LogSessionId} for MediaExtractor.
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
+ /**
* Return Metrics data about the current media container.
*
* @return a {@link PersistableBundle} containing the set of attributes and values
@@ -796,6 +814,7 @@
}
private MediaCas mMediaCas;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
private long mNativeContext;
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd08d8a..f960ff2 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -29,6 +29,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.Camera;
+import android.media.metrics.LogSessionId;
import android.media.permission.Identity;
import android.os.Build;
import android.os.Handler;
@@ -130,6 +131,8 @@
private int mChannelCount;
+ @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;
+
/**
* Default constructor.
*
@@ -165,6 +168,27 @@
}
/**
+ * Sets the {@link LogSessionId} for MediaRecorder.
+ *
+ * @param id the global ID for monitoring the MediaRecorder performance
+ */
+ public void setLogSessionId(@NonNull LogSessionId id) {
+ Objects.requireNonNull(id);
+ mLogSessionId = id;
+ setParameter("log-session-id=" + id.getStringId());
+ }
+
+ /**
+ * Returns the {@link LogSessionId} for MediaRecorder.
+ *
+ * @return the global ID for monitoring the MediaRecorder performance
+ */
+ @NonNull
+ public LogSessionId getLogSessionId() {
+ return mLogSessionId;
+ }
+
+ /**
* Sets a {@link android.hardware.Camera} to use for recording.
*
* <p>Use this function to switch quickly between preview and capture mode without a teardown of
diff --git a/media/java/android/media/metrics/LogSessionId.java b/media/java/android/media/metrics/LogSessionId.java
index f68ef4b..41f3093 100644
--- a/media/java/android/media/metrics/LogSessionId.java
+++ b/media/java/android/media/metrics/LogSessionId.java
@@ -17,20 +17,29 @@
package android.media.metrics;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
+import java.util.Objects;
+
/**
* An instances of this class represents the ID of a log session.
*/
public final class LogSessionId {
- private final String mSessionId;
+ @NonNull private final String mSessionId;
- /* package */ LogSessionId(@NonNull String id) {
- mSessionId = id;
- }
+ /**
+ * A {@link LogSessionId} object which is used to clear any existing session ID.
+ */
+ @NonNull public static final LogSessionId LOG_SESSION_ID_NONE = new LogSessionId("");
/** @hide */
@TestApi
+ public LogSessionId(@NonNull String id) {
+ mSessionId = Objects.requireNonNull(id);
+ }
+
+ /** Returns the ID represented by a string. */
@NonNull
public String getStringId() {
return mSessionId;
@@ -40,4 +49,17 @@
public String toString() {
return mSessionId;
}
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ LogSessionId that = (LogSessionId) o;
+ return Objects.equals(mSessionId, that.mSessionId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSessionId);
+ }
}
diff --git a/media/java/android/media/metrics/PlaybackComponent.java b/media/java/android/media/metrics/PlaybackComponent.java
deleted file mode 100644
index 1cadf3b..0000000
--- a/media/java/android/media/metrics/PlaybackComponent.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 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.media.metrics;
-
-import android.annotation.NonNull;
-
-/**
- * Interface for playback related components used by playback metrics.
- */
-public interface PlaybackComponent {
-
- /**
- * Sets the playback ID of the component.
- */
- void setPlaybackId(@NonNull String playbackId);
-
- /**
- * Gets playback ID.
- */
- @NonNull String getPlaybackId();
-}
diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl
index 96bffee117..3b0f577 100644
--- a/media/java/android/media/session/ISessionManager.aidl
+++ b/media/java/android/media/session/ISessionManager.aidl
@@ -39,6 +39,8 @@
ISession createSession(String packageName, in ISessionCallback sessionCb, String tag,
in Bundle sessionInfo, int userId);
List<MediaSession.Token> getSessions(in ComponentName compName, int userId);
+ MediaSession.Token getMediaKeyEventSession();
+ String getMediaKeyEventSessionPackageName();
void dispatchMediaKeyEvent(String packageName, boolean asSystemService, in KeyEvent keyEvent,
boolean needWakeLock);
boolean dispatchMediaKeyEventToSessionAsSystemService(String packageName,
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 78db750..269b70b 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -195,6 +195,44 @@
}
/**
+ * Gets the media key event session, which would receive a media key event unless specified.
+ * @return The media key event session, which would receive key events by default, unless
+ * the caller has specified the target. Can be {@code null}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @Nullable
+ public MediaSession.Token getMediaKeyEventSession() {
+ try {
+ return mService.getMediaKeyEventSession();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get media key event session", ex);
+ }
+ return null;
+ }
+
+ /**
+ * Gets the package name of the media key event session.
+ * @return The package name of the media key event session or the last session's media button
+ * receiver if the media key event session is {@code null}.
+ * @see #getMediaKeyEventSession()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
+ @NonNull
+ public String getMediaKeyEventSessionPackageName() {
+ try {
+ String packageName = mService.getMediaKeyEventSessionPackageName();
+ return (packageName != null) ? packageName : "";
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Failed to get media key event session", ex);
+ }
+ return "";
+ }
+
+ /**
* Get active sessions for the given user.
* <p>
* This requires the {@link android.Manifest.permission#MEDIA_CONTENT_CONTROL} permission be
@@ -856,7 +894,7 @@
}
/**
- * Add a {@link OnMediaKeyEventDispatchedListener}.
+ * Add a {@link OnMediaKeyEventSessionChangedListener}.
*
* @param executor The executor on which the listener should be invoked
* @param listener A {@link OnMediaKeyEventSessionChangedListener}.
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 85513ca..35249f6 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -258,6 +258,9 @@
ASurfaceTransaction_setHdrMetadata_cta861_3; # introduced=29
ASurfaceTransaction_setHdrMetadata_smpte2086; # introduced=29
ASurfaceTransaction_setOnComplete; # introduced=29
+ ASurfaceTransaction_setPosition; # introduced=31
+ ASurfaceTransaction_setSourceRect; # introduced=31
+ ASurfaceTransaction_setTransform; # introduced=31
ASurfaceTransaction_setVisibility; # introduced=29
ASurfaceTransaction_setZOrder; # introduced=29
ASystemFontIterator_open; # introduced=29
@@ -287,6 +290,8 @@
android_getaddrinfofornetwork; # introduced=23
android_getprocnetwork; # introduced=31
android_setprocnetwork; # introduced=23
+ android_getprocdns; # introduced=31
+ android_setprocdns; # introduced=31
android_setsocknetwork; # introduced=23
android_res_cancel; # introduced=29
android_res_nquery; # introduced=29
diff --git a/native/android/libandroid_net.map.txt b/native/android/libandroid_net.map.txt
index cc8dd72..a6c1b50 100644
--- a/native/android/libandroid_net.map.txt
+++ b/native/android/libandroid_net.map.txt
@@ -16,6 +16,8 @@
android_res_nsend; # llndk
# These functions have been part of the NDK since API 31.
android_getprocnetwork; # llndk
+ android_setprocdns; # llndk
+ android_getprocdns; # llndk
local:
*;
};
diff --git a/native/android/net.c b/native/android/net.c
index d4b8888..e2f36a7 100644
--- a/native/android/net.c
+++ b/native/android/net.c
@@ -90,6 +90,38 @@
return 0;
}
+int android_setprocdns(net_handle_t network) {
+ unsigned netid;
+ if (!getnetidfromhandle(network, &netid)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ int rval = setNetworkForResolv(netid);
+ if (rval < 0) {
+ errno = -rval;
+ rval = -1;
+ }
+ return rval;
+}
+
+int android_getprocdns(net_handle_t *network) {
+ if (network == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ unsigned netid;
+ int rval = getNetworkForDns(&netid);
+ if (rval < 0) {
+ errno = -rval;
+ return -1;
+ }
+
+ *network = gethandlefromnetid(netid);
+ return 0;
+}
+
int android_getaddrinfofornetwork(net_handle_t network,
const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res) {
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index e8cf63f..195fd5e 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -446,6 +446,44 @@
transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay);
}
+void ASurfaceTransaction_setSourceRect(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, const ARect& source) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_VALID_RECT(source);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setCrop(surfaceControl, static_cast<const Rect&>(source));
+}
+
+void ASurfaceTransaction_setPosition(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, const ARect& destination) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+ CHECK_VALID_RECT(destination);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setFrame(surfaceControl, static_cast<const Rect&>(destination));
+}
+
+void ASurfaceTransaction_setTransform(ASurfaceTransaction* aSurfaceTransaction,
+ ASurfaceControl* aSurfaceControl, int32_t transform) {
+ CHECK_NOT_NULL(aSurfaceTransaction);
+ CHECK_NOT_NULL(aSurfaceControl);
+
+ sp<SurfaceControl> surfaceControl = ASurfaceControl_to_SurfaceControl(aSurfaceControl);
+ Transaction* transaction = ASurfaceTransaction_to_Transaction(aSurfaceTransaction);
+
+ transaction->setTransform(surfaceControl, transform);
+ bool transformToInverseDisplay = (NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY & transform) ==
+ NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
+ transaction->setTransformToDisplayInverse(surfaceControl, transformToInverseDisplay);
+}
+
void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* aSurfaceTransaction,
ASurfaceControl* aSurfaceControl,
int8_t transparency) {
diff --git a/packages/Connectivity/framework/Android.bp b/packages/Connectivity/framework/Android.bp
index 3553c1f..80c68f2 100644
--- a/packages/Connectivity/framework/Android.bp
+++ b/packages/Connectivity/framework/Android.bp
@@ -26,6 +26,7 @@
java_library {
name: "framework-connectivity-protos",
sdk_version: "module_current",
+ min_sdk_version: "30",
proto: {
type: "nano",
},
@@ -109,13 +110,15 @@
"-Wall",
"-Werror",
"-Wno-unused-parameter",
+ // Don't warn about S API usage even with
+ // min_sdk 30: the library is only loaded
+ // on S+ devices
+ "-Wno-unguarded-availability",
"-Wthread-safety",
],
shared_libs: [
- "libbase",
"liblog",
"libnativehelper",
- "libnetd_client",
],
header_libs: [
"dnsproxyd_protocol_headers",
@@ -137,12 +140,14 @@
cc_library_shared {
name: "libframework-connectivity-jni",
+ min_sdk_version: "30",
defaults: ["libframework-connectivity-defaults"],
srcs: [
+ "jni/android_net_NetworkUtils.cpp",
"jni/onload.cpp",
],
shared_libs: ["libandroid"],
- static_libs: ["libconnectivityframeworkutils"],
+ stl: "libc++_static",
apex_available: [
"//apex_available:platform",
"com.android.tethering",
@@ -152,6 +157,7 @@
java_library {
name: "framework-connectivity.impl",
sdk_version: "module_current",
+ min_sdk_version: "30",
srcs: [
":framework-connectivity-sources",
],
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index f7c3965..2bf807c4 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -28,6 +28,14 @@
field public static final String ACTION_PROMPT_LOST_VALIDATION = "android.net.action.PROMPT_LOST_VALIDATION";
field public static final String ACTION_PROMPT_PARTIAL_CONNECTIVITY = "android.net.action.PROMPT_PARTIAL_CONNECTIVITY";
field public static final String ACTION_PROMPT_UNVALIDATED = "android.net.action.PROMPT_UNVALIDATED";
+ field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
+ field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
+ field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
+ field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
+ field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
+ field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
+ field public static final int BLOCKED_REASON_NONE = 0; // 0x0
+ field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final String PRIVATE_DNS_MODE_OFF = "off";
field public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
field public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index b19efa3..f3f5533 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -232,8 +232,8 @@
method public final void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
method public final void sendNetworkScore(@IntRange(from=0, to=99) int);
method public final void sendQosCallbackError(int, int);
- method public final void sendQosSessionAvailable(int, int, @NonNull android.telephony.data.EpsBearerQosSessionAttributes);
- method public final void sendQosSessionLost(int, int);
+ method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
+ method public final void sendQosSessionLost(int, int, int);
method public final void sendSocketKeepaliveEvent(int, int);
method @Deprecated public void setLegacySubtype(int, @NonNull String);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
@@ -270,6 +270,7 @@
public final class NetworkCapabilities implements android.os.Parcelable {
method @NonNull public int[] getAdministratorUids();
+ method @Nullable public static String getCapabilityCarrierName(int);
method @Nullable public String getSsid();
method @NonNull public int[] getTransportTypes();
method public boolean isPrivateDnsBroken();
@@ -384,6 +385,7 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.QosSession> CREATOR;
field public static final int TYPE_EPS_BEARER = 1; // 0x1
+ field public static final int TYPE_NR_BEARER = 2; // 0x2
}
public interface QosSessionAttributes {
diff --git a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
index c7c0bee..e8bb42d 100644
--- a/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
+++ b/packages/Connectivity/framework/jni/android_net_NetworkUtils.cpp
@@ -37,7 +37,6 @@
#include <utils/Log.h>
#include <utils/misc.h>
-#include "NetdClient.h"
#include "jni.h"
extern "C" {
@@ -113,19 +112,14 @@
}
static jboolean android_net_utils_bindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz,
- jint netId)
+ jint netId, jlong netHandle)
{
- return (jboolean) !setNetworkForResolv(netId);
+ return (jboolean) !android_setprocdns(netHandle);
}
-static jint android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jobject javaFd,
- jint netId) {
- return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
-}
-
-static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
-{
- return (jboolean) !queryUserAccess(uid, netId);
+static jint android_net_utils_bindSocketToNetworkHandle(JNIEnv *env, jobject thiz, jobject javaFd,
+ jlong netHandle) {
+ return android_setsocknetwork(netHandle, AFileDescriptor_getFD(env, javaFd));
}
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
@@ -137,7 +131,7 @@
return true;
}
-static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkQuery(JNIEnv *env, jobject thiz, jlong netHandle,
jstring dname, jint ns_class, jint ns_type, jint flags) {
const jsize javaCharsCount = env->GetStringLength(dname);
const jsize byteCountUTF8 = env->GetStringUTFLength(dname);
@@ -147,7 +141,8 @@
std::vector<char> queryname(byteCountUTF8 + 1, 0);
env->GetStringUTFRegion(dname, 0, javaCharsCount, queryname.data());
- int fd = resNetworkQuery(netId, queryname.data(), ns_class, ns_type, flags);
+
+ int fd = android_res_nquery(netHandle, queryname.data(), ns_class, ns_type, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkQuery", -fd);
@@ -157,12 +152,12 @@
return jniCreateFileDescriptor(env, fd);
}
-static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jint netId,
+static jobject android_net_utils_resNetworkSend(JNIEnv *env, jobject thiz, jlong netHandle,
jbyteArray msg, jint msgLen, jint flags) {
uint8_t data[MAXCMDSIZE];
checkLenAndCopy(env, msg, msgLen, data);
- int fd = resNetworkSend(netId, data, msgLen, flags);
+ int fd = android_res_nsend(netHandle, data, msgLen, flags);
if (fd < 0) {
jniThrowErrnoException(env, "resNetworkSend", -fd);
@@ -177,7 +172,7 @@
int rcode;
std::vector<uint8_t> buf(MAXPACKETSIZE, 0);
- int res = resNetworkResult(fd, &rcode, buf.data(), MAXPACKETSIZE);
+ int res = android_res_nresult(fd, &rcode, buf.data(), MAXPACKETSIZE);
jniSetFileDescriptorOfFD(env, javaFd, -1);
if (res < 0) {
jniThrowErrnoException(env, "resNetworkResult", -res);
@@ -201,23 +196,22 @@
static void android_net_utils_resNetworkCancel(JNIEnv *env, jobject thiz, jobject javaFd) {
int fd = AFileDescriptor_getFD(env, javaFd);
- resNetworkCancel(fd);
+ android_res_cancel(fd);
jniSetFileDescriptorOfFD(env, javaFd, -1);
}
static jobject android_net_utils_getDnsNetwork(JNIEnv *env, jobject thiz) {
- unsigned dnsNetId = 0;
- if (int res = getNetworkForDns(&dnsNetId) < 0) {
- jniThrowErrnoException(env, "getDnsNetId", -res);
+ net_handle_t dnsNetHandle = NETWORK_UNSPECIFIED;
+ if (int res = android_getprocdns(&dnsNetHandle) < 0) {
+ jniThrowErrnoException(env, "getDnsNetwork", -res);
return nullptr;
}
- bool privateDnsBypass = dnsNetId & NETID_USE_LOCAL_NAMESERVERS;
static jclass class_Network = MakeGlobalRefOrDie(
env, FindClassOrDie(env, "android/net/Network"));
- static jmethodID ctor = env->GetMethodID(class_Network, "<init>", "(IZ)V");
- return env->NewObject(
- class_Network, ctor, dnsNetId & ~NETID_USE_LOCAL_NAMESERVERS, privateDnsBypass);
+ static jmethodID method = env->GetStaticMethodID(class_Network, "fromNetworkHandle",
+ "(J)Landroid/net/Network;");
+ return env->CallStaticObjectMethod(class_Network, method, static_cast<jlong>(dnsNetHandle));
}
static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
@@ -266,13 +260,12 @@
{ "bindProcessToNetworkHandle", "(J)Z", (void*) android_net_utils_bindProcessToNetworkHandle },
{ "getBoundNetworkHandleForProcess", "()J", (void*) android_net_utils_getBoundNetworkHandleForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
- { "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
- { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
+ { "bindSocketToNetworkHandle", "(Ljava/io/FileDescriptor;J)I", (void*) android_net_utils_bindSocketToNetworkHandle },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
- { "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
- { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
+ { "resNetworkSend", "(J[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
+ { "resNetworkQuery", "(JLjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
{ "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
{ "getDnsNetwork", "()Landroid/net/Network;", (void*) android_net_utils_getDnsNetwork },
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index 20ff93f..a73d76e 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -829,6 +829,94 @@
})
public @interface PrivateDnsMode {}
+ /**
+ * Flag to indicate that an app is not subject to any restrictions that could result in its
+ * network access blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_NONE = 0;
+
+ /**
+ * Flag to indicate that an app is subject to Battery saver restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_BATTERY_SAVER = 1 << 0;
+
+ /**
+ * Flag to indicate that an app is subject to Doze restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_DOZE = 1 << 1;
+
+ /**
+ * Flag to indicate that an app is subject to App Standby restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_APP_STANDBY = 1 << 2;
+
+ /**
+ * Flag to indicate that an app is subject to Restricted mode restrictions that would
+ * result in its network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3;
+
+ /**
+ * Flag to indicate that an app is subject to Data saver restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_DATA_SAVER = 1 << 16;
+
+ /**
+ * Flag to indicate that an app is subject to user restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 1 << 17;
+
+ /**
+ * Flag to indicate that an app is subject to Device admin restrictions that would
+ * result in its metered network access being blocked.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 1 << 18;
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = {"BLOCKED_"}, value = {
+ BLOCKED_REASON_NONE,
+ BLOCKED_REASON_BATTERY_SAVER,
+ BLOCKED_REASON_DOZE,
+ BLOCKED_REASON_APP_STANDBY,
+ BLOCKED_REASON_RESTRICTED_MODE,
+ BLOCKED_METERED_REASON_DATA_SAVER,
+ BLOCKED_METERED_REASON_USER_RESTRICTED,
+ BLOCKED_METERED_REASON_ADMIN_DISABLED,
+ })
+ public @interface BlockedReason {}
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
@@ -3234,7 +3322,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/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index c5464d3..cbd6193 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -22,6 +22,7 @@
import android.net.NetworkScore;
import android.net.QosSession;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
/**
* Interface for NetworkAgents to send network properties.
@@ -37,6 +38,7 @@
void sendSocketKeepaliveEvent(int slot, int reason);
void sendUnderlyingNetworks(in @nullable List<Network> networks);
void sendEpsQosSessionAvailable(int callbackId, in QosSession session, in EpsBearerQosSessionAttributes attributes);
+ void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes);
void sendQosSessionLost(int qosCallbackId, in QosSession session);
void sendQosCallbackError(int qosCallbackId, int exceptionType);
}
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/IQosCallback.aidl b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
index 91c7575..c973541 100644
--- a/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
+++ b/packages/Connectivity/framework/src/android/net/IQosCallback.aidl
@@ -19,6 +19,7 @@
import android.os.Bundle;
import android.net.QosSession;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
/**
* AIDL interface for QosCallback
@@ -29,6 +30,8 @@
{
void onQosEpsBearerSessionAvailable(in QosSession session,
in EpsBearerQosSessionAttributes attributes);
+ void onNrQosSessionAvailable(in QosSession session,
+ in NrQosSessionAttributes attributes);
void onQosSessionLost(in QosSession session);
void onError(in int type);
}
diff --git a/packages/Connectivity/framework/src/android/net/Network.java b/packages/Connectivity/framework/src/android/net/Network.java
index 41fad63..1f49033 100644
--- a/packages/Connectivity/framework/src/android/net/Network.java
+++ b/packages/Connectivity/framework/src/android/net/Network.java
@@ -92,6 +92,7 @@
// value in the native/android/net.c NDK implementation.
private static final long HANDLE_MAGIC = 0xcafed00dL;
private static final int HANDLE_MAGIC_SIZE = 32;
+ private static final int USE_LOCAL_NAMESERVERS_FLAG = 0x80000000;
// A boolean to control how getAllByName()/getByName() behaves in the face
// of Private DNS.
@@ -189,7 +190,7 @@
*/
public int getNetIdForResolv() {
return mPrivateDnsBypass
- ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag.
+ ? (USE_LOCAL_NAMESERVERS_FLAG | netId) // Non-portable DNS resolution flag.
: netId;
}
@@ -452,12 +453,13 @@
throw new IllegalArgumentException(
"Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
}
- if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
- || networkHandle < 0) {
+ if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC) {
throw new IllegalArgumentException(
"Value passed to fromNetworkHandle() is not a network handle.");
}
- return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
+ final int netIdForResolv = (int) (networkHandle >>> HANDLE_MAGIC_SIZE);
+ return new Network((netIdForResolv & ~USE_LOCAL_NAMESERVERS_FLAG),
+ ((netIdForResolv & USE_LOCAL_NAMESERVERS_FLAG) != 0) /* privateDnsBypass */);
}
/**
@@ -485,7 +487,7 @@
if (netId == 0) {
return 0L; // make this zero condition obvious for debugging
}
- return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
+ return (((long) getNetIdForResolv()) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
}
// implement the Parcelable interface
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index b3d9616..f7cd4f6 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -32,6 +32,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -1160,29 +1161,37 @@
/**
- * Sends the attributes of Eps Bearer Qos Session back to the Application
+ * Sends the attributes of Qos Session back to the Application
*
* @param qosCallbackId the callback id that the session belongs to
- * @param sessionId the unique session id across all Eps Bearer Qos Sessions
- * @param attributes the attributes of the Eps Qos Session
+ * @param sessionId the unique session id across all Qos Sessions
+ * @param attributes the attributes of the Qos Session
*/
public final void sendQosSessionAvailable(final int qosCallbackId, final int sessionId,
- @NonNull final EpsBearerQosSessionAttributes attributes) {
+ @NonNull final QosSessionAttributes attributes) {
Objects.requireNonNull(attributes, "The attributes must be non-null");
- queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
- new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
- attributes));
+ if (attributes instanceof EpsBearerQosSessionAttributes) {
+ queueOrSendMessage(ra -> ra.sendEpsQosSessionAvailable(qosCallbackId,
+ new QosSession(sessionId, QosSession.TYPE_EPS_BEARER),
+ (EpsBearerQosSessionAttributes)attributes));
+ } else if (attributes instanceof NrQosSessionAttributes) {
+ queueOrSendMessage(ra -> ra.sendNrQosSessionAvailable(qosCallbackId,
+ new QosSession(sessionId, QosSession.TYPE_NR_BEARER),
+ (NrQosSessionAttributes)attributes));
+ }
}
/**
- * Sends event that the Eps Qos Session was lost.
+ * Sends event that the Qos Session was lost.
*
* @param qosCallbackId the callback id that the session belongs to
- * @param sessionId the unique session id across all Eps Bearer Qos Sessions
+ * @param sessionId the unique session id across all Qos Sessions
+ * @param qosSessionType the session type {@code QosSesson#QosSessionType}
*/
- public final void sendQosSessionLost(final int qosCallbackId, final int sessionId) {
+ public final void sendQosSessionLost(final int qosCallbackId,
+ final int sessionId, final int qosSessionType) {
queueOrSendMessage(ra -> ra.sendQosSessionLost(qosCallbackId,
- new QosSession(sessionId, QosSession.TYPE_EPS_BEARER)));
+ new QosSession(sessionId, qosSessionType)));
}
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
index 27f7ee2..f50b018 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -711,6 +711,23 @@
return ((mNetworkCapabilities & CONNECTIVITY_MANAGED_CAPABILITIES) != 0);
}
+ /**
+ * Get the name of the given capability that carriers use.
+ * If the capability does not have a carrier-name, returns null.
+ *
+ * @param capability The capability to get the carrier-name of.
+ * @return The carrier-name of the capability, or null if it doesn't exist.
+ * @hide
+ */
+ @SystemApi
+ public static @Nullable String getCapabilityCarrierName(@NetCapability int capability) {
+ if (capability == NET_CAPABILITY_ENTERPRISE) {
+ return capabilityNameOf(capability);
+ } else {
+ return null;
+ }
+ }
+
private void combineNetCapabilities(@NonNull NetworkCapabilities nc) {
final long wantedCaps = this.mNetworkCapabilities | nc.mNetworkCapabilities;
final long unwantedCaps =
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/Connectivity/framework/src/android/net/NetworkUtils.java b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
index 16ae55f..16a49bc 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkUtils.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkUtils.java
@@ -92,18 +92,28 @@
@Deprecated
public native static boolean bindProcessToNetworkForHostResolution(int netId);
+ private static native int bindSocketToNetworkHandle(FileDescriptor fd, long netHandle);
+
/**
* Explicitly binds {@code fd} to the network designated by {@code netId}. This
* overrides any binding via {@link #bindProcessToNetwork}.
* @return 0 on success or negative errno on failure.
*/
- public static native int bindSocketToNetwork(FileDescriptor fd, int netId);
+ public static int bindSocketToNetwork(FileDescriptor fd, int netId) {
+ return bindSocketToNetworkHandle(fd, new Network(netId).getNetworkHandle());
+ }
/**
* Determine if {@code uid} can access network designated by {@code netId}.
* @return {@code true} if {@code uid} can access network, {@code false} otherwise.
*/
- public native static boolean queryUserAccess(int uid, int netId);
+ public static boolean queryUserAccess(int uid, int netId) {
+ // TODO (b/183485986): remove this method
+ return false;
+ }
+
+ private static native FileDescriptor resNetworkSend(
+ long netHandle, byte[] msg, int msglen, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
@@ -111,8 +121,13 @@
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkSend(
- int netId, byte[] msg, int msglen, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkSend(
+ int netId, byte[] msg, int msglen, int flags) throws ErrnoException {
+ return resNetworkSend(new Network(netId).getNetworkHandle(), msg, msglen, flags);
+ }
+
+ private static native FileDescriptor resNetworkQuery(
+ long netHandle, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
/**
* DNS resolver series jni method.
@@ -121,8 +136,11 @@
* {@code flags} is an additional config to control actual querying behavior.
* @return a file descriptor to watch for read events
*/
- public static native FileDescriptor resNetworkQuery(
- int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException;
+ public static FileDescriptor resNetworkQuery(
+ int netId, String dname, int nsClass, int nsType, int flags) throws ErrnoException {
+ return resNetworkQuery(new Network(netId).getNetworkHandle(), dname, nsClass, nsType,
+ flags);
+ }
/**
* DNS resolver series jni method.
diff --git a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
index bdb4ad6..de0fc24 100644
--- a/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
+++ b/packages/Connectivity/framework/src/android/net/QosCallbackConnection.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import com.android.internal.annotations.VisibleForTesting;
@@ -84,6 +85,25 @@
}
/**
+ * Called when either the {@link NrQosSessionAttributes} has changed or on the first time
+ * the attributes have become available.
+ *
+ * @param session the session that is now available
+ * @param attributes the corresponding attributes of session
+ */
+ @Override
+ public void onNrQosSessionAvailable(@NonNull final QosSession session,
+ @NonNull final NrQosSessionAttributes attributes) {
+
+ mExecutor.execute(() -> {
+ final QosCallback callback = mCallback;
+ if (callback != null) {
+ callback.onQosSessionAvailable(session, attributes);
+ }
+ });
+ }
+
+ /**
* Called when the session is lost.
*
* @param session the session that was lost
diff --git a/packages/Connectivity/framework/src/android/net/QosSession.java b/packages/Connectivity/framework/src/android/net/QosSession.java
index 4f3bb77..93f2ff2 100644
--- a/packages/Connectivity/framework/src/android/net/QosSession.java
+++ b/packages/Connectivity/framework/src/android/net/QosSession.java
@@ -36,6 +36,11 @@
*/
public static final int TYPE_EPS_BEARER = 1;
+ /**
+ * The {@link QosSession} is a NR Session.
+ */
+ public static final int TYPE_NR_BEARER = 2;
+
private final int mSessionId;
private final int mSessionType;
@@ -100,6 +105,7 @@
*/
@IntDef(value = {
TYPE_EPS_BEARER,
+ TYPE_NR_BEARER,
})
@interface QosSessionType {}
diff --git a/packages/Connectivity/service/Android.bp b/packages/Connectivity/service/Android.bp
index 37dd9ff..9d1bb0f 100644
--- a/packages/Connectivity/service/Android.bp
+++ b/packages/Connectivity/service/Android.bp
@@ -25,7 +25,7 @@
cc_library_shared {
name: "libservice-connectivity",
- // TODO: build against the NDK (sdk_version: "30" for example)
+ min_sdk_version: "30",
cflags: [
"-Wall",
"-Werror",
@@ -36,13 +36,13 @@
"jni/com_android_server_TestNetworkService.cpp",
"jni/onload.cpp",
],
+ stl: "libc++_static",
+ header_libs: [
+ "libbase_headers",
+ ],
shared_libs: [
- "libbase",
"liblog",
"libnativehelper",
- // TODO: remove dependency on ifc_[add/del]_address by having Java code to add/delete
- // addresses, and remove dependency on libnetutils.
- "libnetutils",
],
apex_available: [
"com.android.tethering",
@@ -52,6 +52,7 @@
java_library {
name: "service-connectivity-pre-jarjar",
sdk_version: "system_server_current",
+ min_sdk_version: "30",
srcs: [
":connectivity-service-srcs",
":framework-connectivity-shared-srcs",
@@ -90,6 +91,7 @@
java_library {
name: "service-connectivity-protos",
sdk_version: "system_current",
+ min_sdk_version: "30",
proto: {
type: "nano",
},
@@ -106,6 +108,7 @@
java_library {
name: "service-connectivity",
sdk_version: "system_server_current",
+ min_sdk_version: "30",
installable: true,
static_libs: [
"service-connectivity-pre-jarjar",
diff --git a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
index fa4501a..d783738 100644
--- a/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
+++ b/packages/Connectivity/service/ServiceConnectivityResources/Android.bp
@@ -22,6 +22,7 @@
android_app {
name: "ServiceConnectivityResources",
sdk_version: "module_current",
+ min_sdk_version: "30",
resource_dirs: [
"res",
],
diff --git a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
index 36a6fde..e7a40e5 100644
--- a/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
+++ b/packages/Connectivity/service/jni/com_android_server_TestNetworkService.cpp
@@ -35,8 +35,6 @@
#include <log/log.h>
-#include "netutils/ifc.h"
-
#include "jni.h"
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
@@ -48,9 +46,8 @@
//------------------------------------------------------------------------------
static void throwException(JNIEnv* env, int error, const char* action, const char* iface) {
- const std::string& msg =
- android::base::StringPrintf("Error %s %s: %s", action, iface, strerror(error));
-
+ const std::string& msg = "Error: " + std::string(action) + " " + std::string(iface) + ": "
+ + std::string(strerror(error));
jniThrowException(env, "java/lang/IllegalStateException", msg.c_str());
}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6c66b5..5e69a4e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -17,8 +17,8 @@
// TODO(b/149540986): revert this change.
static_libs: [
- // All other dependent components should be put in
- // "SettingsLibDependenciesWithoutWifiTracker".
+ // All other dependent components should be put in
+ // "SettingsLibDependenciesWithoutWifiTracker".
"WifiTrackerLib",
],
@@ -27,7 +27,10 @@
resource_dirs: ["res"],
- srcs: ["src/**/*.java", "src/**/*.kt"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
min_sdk_version: "21",
@@ -68,6 +71,7 @@
"SettingsLibUsageProgressBarPreference",
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibTwoTargetPreference",
+ "SettingsLibSettingsTransition",
],
}
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index 8c208e3..0390e86 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -21,7 +21,7 @@
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingStart="@dimen/app_preference_padding_start"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<LinearLayout
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
- android:minWidth="56dp"
+ android:minWidth="@dimen/app_icon_min_width"
android:orientation="horizontal"
android:paddingEnd="8dp"
android:paddingTop="4dp"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 231babe..dd9fc2c 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -23,5 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.cellbroadcast",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
new file mode 100644
index 0000000..24d53ab
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<androidx.coordinatorlayout.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/content_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:transitionGroup="true">
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="180dp"
+ android:theme="@style/Theme.CollapsingToolbar.Settings">
+
+ <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout
+ android:id="@+id/collapsing_toolbar"
+ android:background="?android:attr/colorPrimary"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:maxLines="3"
+ app:contentScrim="?android:attr/colorPrimary"
+ app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
+ app:statusBarScrim="?android:attr/colorPrimary"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed"
+ app:expandedTitleMarginStart="18dp"
+ app:expandedTitleMarginEnd="18dp"
+ app:toolbarId="@id/action_bar">
+
+ <Toolbar
+ android:id="@+id/action_bar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:theme="?android:attr/actionBarTheme"
+ app:layout_collapseMode="pin"/>
+
+ </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout>
+ </com.google.android.material.appbar.AppBarLayout>
+
+ <FrameLayout
+ android:id="@+id/content_frame"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
index e376930..c799b99 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
@@ -14,47 +14,23 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.coordinatorlayout.widget.CoordinatorLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
+<!-- The main content view -->
+<LinearLayout
android:id="@+id/content_parent"
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:transitionGroup="true">
-
- <com.google.android.material.appbar.AppBarLayout
- android:id="@+id/app_bar"
+ android:fitsSystemWindows="true"
+ android:transitionGroup="true"
+ android:orientation="vertical">
+ <Toolbar
+ android:id="@+id/action_bar"
+ style="?android:attr/actionBarStyle"
android:layout_width="match_parent"
- android:layout_height="180dp"
- android:theme="@style/Theme.CollapsingToolbar.Settings">
-
- <com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout
- android:id="@+id/collapsing_toolbar"
- android:background="?android:attr/colorPrimary"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- app:maxLines="3"
- app:contentScrim="?android:attr/colorPrimary"
- app:collapsedTitleTextAppearance="@style/CollapsingToolbarTitle.Collapsed"
- app:statusBarScrim="?android:attr/colorPrimary"
- app:layout_scrollFlags="scroll|exitUntilCollapsed"
- app:expandedTitleMarginStart="18dp"
- app:expandedTitleMarginEnd="18dp"
- app:toolbarId="@id/action_bar">
-
- <Toolbar
- android:id="@+id/action_bar"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:theme="?android:attr/actionBarTheme"
- app:layout_collapseMode="pin"/>
-
- </com.android.settingslib.collapsingtoolbar.AdjustableToolbarLayout>
- </com.google.android.material.appbar.AppBarLayout>
-
+ android:layout_height="wrap_content"
+ android:theme="?android:attr/actionBarTheme" />
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
+ android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
deleted file mode 100644
index c799b99..0000000
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/toolbar_base_layout.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-<!-- The main content view -->
-<LinearLayout
- android:id="@+id/content_parent"
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:fitsSystemWindows="true"
- android:transitionGroup="true"
- android:orientation="vertical">
- <Toolbar
- android:id="@+id/action_bar"
- style="?android:attr/actionBarStyle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:theme="?android:attr/actionBarTheme" />
- <FrameLayout
- android:id="@+id/content_frame"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-</LinearLayout>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
index e1a64d4..1157a34 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
@@ -17,10 +17,10 @@
<resources>
<style name="CollapsingToolbarTitle.Collapsed"
parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
</style>
- <style name="CollapsingToolbarTitle"
- parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <style name="CollapsingToolbarTitle" parent="CollapsingToolbarTitle.Collapsed">
<item name="android:textSize">36sp</item>
</style>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index ad94cd03..957bac7 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -24,7 +24,6 @@
import android.widget.Toolbar;
import androidx.annotation.Nullable;
-import androidx.core.os.BuildCompat;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.appbar.CollapsingToolbarLayout;
@@ -41,15 +40,8 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // TODO(b/181723278): Update the version check after SDK for S is finalized
- // The collapsing toolbar is only supported if the android platform version is S or higher.
- // Otherwise the regular action bar will be shown.
- if (BuildCompat.isAtLeastS()) {
- super.setContentView(R.layout.collapsing_toolbar_base_layout);
- mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
- } else {
- super.setContentView(R.layout.toolbar_base_layout);
- }
+ super.setContentView(R.layout.collapsing_toolbar_base_layout);
+ mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar);
final Toolbar toolbar = findViewById(R.id.action_bar);
setActionBar(toolbar);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
new file mode 100644
index 0000000..c4c74ff
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseFragment.java
@@ -0,0 +1,78 @@
+/*
+ * 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.settingslib.collapsingtoolbar;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.Toolbar;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+
+/**
+ * A base fragment that has a collapsing toolbar layout for enabling the collapsing toolbar design.
+ */
+public abstract class CollapsingToolbarBaseFragment extends Fragment {
+
+ @Nullable
+ private CollapsingToolbarLayout mCollapsingToolbarLayout;
+ @NonNull
+ private Toolbar mToolbar;
+ @NonNull
+ private FrameLayout mContentFrameLayout;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.collapsing_toolbar_base_layout, container,
+ false);
+ mCollapsingToolbarLayout = view.findViewById(R.id.collapsing_toolbar);
+ mToolbar = view.findViewById(R.id.action_bar);
+ mContentFrameLayout = view.findViewById(R.id.content_frame);
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ requireActivity().setActionBar(mToolbar);
+ }
+
+ /**
+ * Return the collapsing toolbar layout.
+ */
+ @Nullable
+ public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+ return mCollapsingToolbarLayout;
+ }
+
+ /**
+ * Return the content frame layout.
+ */
+ @NonNull
+ public FrameLayout getContentFrameLayout() {
+ return mContentFrameLayout;
+ }
+}
diff --git a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
index 5496a01..7a550ae 100644
--- a/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
+++ b/packages/SettingsLib/FooterPreference/res/layout/preference_footer.xml
@@ -23,6 +23,7 @@
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:background="?android:attr/selectableItemBackground"
+ android:orientation="vertical"
android:clipToPadding="false">
<LinearLayout
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml
new file mode 100644
index 0000000..5567790
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/image_frame.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/icon_min_width"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingLeft="0dp"
+ android:paddingStart="0dp"
+ android:paddingRight="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:maxWidth="48dp"
+ app:maxHeight="48dp"/>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml b/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
deleted file mode 100644
index 4a1b089..0000000
--- a/packages/SettingsLib/SettingsTheme/res/layout/preference_category_settings.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
- -->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingRight="?android:attr/listPreferredItemPaddingRight"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:background="?android:attr/selectableItemBackground"
- android:baselineAligned="false"
- android:layout_marginTop="16dp"
- android:gravity="center_vertical"
- style="@style/PreferenceCategoryStartMargin">
-
- <RelativeLayout
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:paddingTop="8dp"
- android:paddingBottom="8dp">
-
- <TextView
- android:id="@android:id/title"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- style="@style/PreferenceCategoryTitleTextStyle"/>
-
- <TextView
- android:id="@android:id/summary"
- android:ellipsize="end"
- android:singleLine="true"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_alignLeft="@android:id/title"
- android:layout_alignStart="@android:id/title"
- android:layout_gravity="start"
- android:textAlignment="viewStart"
- android:textColor="?android:attr/textColorSecondary"
- android:maxLines="10"
- style="@style/PreferenceSummaryTextStyle"/>
- </RelativeLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml
new file mode 100644
index 0000000..87977bd
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_dropdown_preference.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<!-- We use a FrameLayout as we want to place the invisible spinner on top of the other views -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <!-- This spinner should be invisible in the layout and take up no space, when the Preference
+ is clicked the dropdown will appear from this location on screen. -->
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/preference_dropdown_padding_start"
+ android:layout_marginLeft="@dimen/preference_dropdown_padding_start"
+ android:visibility="invisible" />
+
+ <include layout="@layout/settings_preference" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml
new file mode 100644
index 0000000..3a289a7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settings_preference.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:background="?android:attr/selectableItemBackground"
+ android:clipToPadding="false"
+ android:baselineAligned="false">
+
+ <include layout="@layout/image_frame"/>
+
+ <RelativeLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:ellipsize="marquee"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@android:id/title"
+ android:layout_alignLeft="@android:id/title"
+ android:layout_alignStart="@android:id/title"
+ android:layout_gravity="start"
+ android:textAlignment="viewStart"
+ android:textColor="?android:attr/textColorSecondary"
+ android:maxLines="10"
+ style="@style/PreferenceSummaryTextStyle"/>
+
+ </RelativeLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="end|center_vertical"
+ android:paddingLeft="16dp"
+ android:paddingStart="16dp"
+ android:paddingRight="0dp"
+ android:paddingEnd="0dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
new file mode 100644
index 0000000..acf06c4
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <bool name="config_icon_space_reserved">false</bool>
+ <bool name="config_allow_divider">false</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
new file mode 100644
index 0000000..17b6805
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <dimen name="preference_title_font_size">20sp</dimen>
+ <dimen name="icon_min_width">48dp</dimen>
+ <dimen name="preference_padding_start">24dp</dimen>
+ <dimen name="preference_padding_end">24dp</dimen>
+ <dimen name="app_preference_padding_start">20dp</dimen>
+ <dimen name="app_icon_min_width">52dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml
new file mode 100644
index 0000000..a3bb1da
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <bool name="config_icon_space_reserved">true</bool>
+ <bool name="config_allow_divider">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 9485655..009ae6b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -17,4 +17,10 @@
<resources>
<dimen name="secondary_app_icon_size">32dp</dimen>
+ <dimen name="preference_title_font_size">16sp</dimen>
+ <dimen name="icon_min_width">56dp</dimen>
+ <dimen name="preference_padding_start">?android:attr/dialogPreferredPadding</dimen>
+ <dimen name="preference_padding_end">?android:attr/dialogPreferredPadding</dimen>
+ <dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen>
+ <dimen name="app_icon_min_width">56dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index a6623b0..f24e008 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -15,16 +15,8 @@
limitations under the License.
-->
<resources>
- <style name="TextAppearance.CategoryTitle"
- parent="@*android:style/TextAppearance.DeviceDefault.Body2">
- <item name="android:textAllCaps">true</item>
- <item name="android:textSize">11sp</item>
- <!-- 0.8 Spacing, 0.8/11 = 0.072727273 -->
- <item name="android:letterSpacing">0.072727273</item>
- </style>
-
- <style name="PreferenceCategoryStartMargin">
- <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingLeft</item>
- <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <style name="TextAppearance.PreferenceTitle"
+ parent="@*android:style/TextAppearance.DeviceDefault.ListItem">
+ <item name="android:textSize">@dimen/preference_title_font_size</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
index 6ca8f577..17596ac2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
@@ -16,17 +16,54 @@
-->
<resources>
<style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay">
- <item name="footerPreferenceStyle">@style/Preference.Material</item>
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
- <!-- For preference category color -->
- <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle
- </item>
- <item name="preferenceCategoryTitleTextColor">?android:attr/textColorSecondary</item>
+ <item name="preferenceStyle">@style/SettingsPreference</item>
+ <item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference</item>
+ <item name="dialogPreferenceStyle">@style/SettingsPreference</item>
+ <item name="editTextPreferenceStyle">@style/SettingsEditTextPreference</item>
+ <item name="dropdownPreferenceStyle">@style/SettingsDropdownPreference</item>
+ <item name="switchPreferenceStyle">@style/SettingsSwitchPreference</item>
+ <item name="seekBarPreferenceStyle">@style/SettingsSeekbarPreference</item>
+ <item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
<style name="SettingsCategoryPreference" parent="@style/Preference.Category.Material">
- <item name="android:layout">@layout/preference_category_settings</item>
- <item name="allowDividerAbove">false</item>
- <item name="allowDividerBelow">false</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ <item name="allowDividerAbove">@bool/config_allow_divider</item>
+ <item name="allowDividerBelow">@bool/config_allow_divider</item>
+ </style>
+
+ <style name="SettingsPreference" parent="@style/Preference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsCheckBoxPreference" parent="@style/Preference.CheckBoxPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsEditTextPreference"
+ parent="@style/Preference.DialogPreference.EditTextPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsDropdownPreference" parent="@style/Preference.DropDown.Material">
+ <item name="layout">@layout/settings_dropdown_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsSwitchPreference" parent="@style/Preference.SwitchPreference.Material">
+ <item name="layout">@layout/settings_preference</item>
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingsSeekbarPreference" parent="@style/Preference.SeekBarPreference.Material">
+ <item name="iconSpaceReserved">@bool/config_icon_space_reserved</item>
+ </style>
+
+ <style name="SettingFooterPreference" parent="@style/Preference.Material">
+ <item name="allowDividerAbove">@bool/config_allow_divider</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
new file mode 100644
index 0000000..36ca684
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<resources>
+ <!-- Only using in Settings application -->
+ <style name="Theme.SettingsBase" parent="@android:style/Theme.DeviceDefault.Settings" >
+ <item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle</item>
+ <item name="android:listPreferredItemPaddingStart">@dimen/preference_padding_start</item>
+ <item name="android:listPreferredItemPaddingEnd">@dimen/preference_padding_end</item>
+ <item name="preferenceTheme">@style/PreferenceTheme</item>
+ </style>
+
+ <!-- Using in SubSettings page including injected settings page -->
+ <style name="Theme.SubSettingsBase" parent="Theme.SettingsBase">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
new file mode 100644
index 0000000..c12f6f7
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -0,0 +1,22 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibSettingsTransition",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "com.google.android.material_material",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/SettingsTransition/AndroidManifest.xml b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
new file mode 100644
index 0000000..11660a5f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.transition">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
similarity index 79%
rename from packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
rename to packages/SettingsLib/SettingsTransition/res/values/dimens.xml
index 4f40256..0630ca8 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-sw360dp/styles.xml
+++ b/packages/SettingsLib/SettingsTransition/res/values/dimens.xml
@@ -15,8 +15,5 @@
-->
<resources>
- <style name="PreferenceCategoryStartMargin">
- <item name="android:paddingLeft">24dp</item>
- <item name="android:paddingStart">24dp</item>
- </style>
-</resources>
+ <dimen name="settings_shared_axis_x_slide_distance">96dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
new file mode 100644
index 0000000..f99fda0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTransition/src/com/android/settingslib/transition/SettingsTransitionHelper.java
@@ -0,0 +1,103 @@
+/*
+ * 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.settingslib.transition;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.view.Window;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+
+import com.google.android.material.transition.platform.MaterialSharedAxis;
+import com.google.android.material.transition.platform.SlideDistanceProvider;
+
+/**
+ * A helper class to apply Settings Transition
+ */
+public class SettingsTransitionHelper {
+
+ private static final String TAG = "SettingsTransitionHelper";
+ private static final long DURATION = 450L;
+
+ private static MaterialSharedAxis createSettingsSharedAxis(Context context, boolean forward) {
+ final MaterialSharedAxis transition = new MaterialSharedAxis(MaterialSharedAxis.X, forward);
+ transition.excludeTarget(android.R.id.statusBarBackground, true);
+ transition.excludeTarget(android.R.id.navigationBarBackground, true);
+
+ final SlideDistanceProvider forwardDistanceProvider =
+ (SlideDistanceProvider) transition.getPrimaryAnimatorProvider();
+ final int distance = context.getResources().getDimensionPixelSize(
+ R.dimen.settings_shared_axis_x_slide_distance);
+ forwardDistanceProvider.setSlideDistance(distance);
+ transition.setDuration(DURATION);
+
+ final Interpolator interpolator =
+ AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_extra_slow_in);
+ transition.setInterpolator(interpolator);
+
+ // TODO(b/177480673): Update fade through threshold once (cl/362065364) is released
+
+ return transition;
+ }
+
+ /**
+ * Apply the forward transition to the {@link Activity}, including Exit Transition and Enter
+ * Transition.
+ *
+ * The Exit Transition takes effect when leaving the page, while the Enter Transition is
+ * triggered when the page is launched/entering.
+ */
+ public static void applyForwardTransition(Activity activity) {
+ if (activity == null) {
+ Log.w(TAG, "applyForwardTransition: Invalid activity!");
+ return;
+ }
+ final Window window = activity.getWindow();
+ if (window == null) {
+ Log.w(TAG, "applyForwardTransition: Invalid window!");
+ return;
+ }
+ final MaterialSharedAxis forward = createSettingsSharedAxis(activity, true);
+ window.setExitTransition(forward);
+ window.setEnterTransition(forward);
+ }
+
+ /**
+ * Apply the backward transition to the {@link Activity}, including Return Transition and
+ * Reenter Transition.
+ *
+ * Return Transition will be used to move Views out of the scene when the Window is preparing
+ * to close. Reenter Transition will be used to move Views in to the scene when returning from a
+ * previously-started Activity.
+ */
+ public static void applyBackwardTransition(Activity activity) {
+ if (activity == null) {
+ Log.w(TAG, "applyBackwardTransition: Invalid activity!");
+ return;
+ }
+ final Window window = activity.getWindow();
+ if (window == null) {
+ Log.w(TAG, "applyBackwardTransition: Invalid window!");
+ return;
+ }
+ final MaterialSharedAxis backward = createSettingsSharedAxis(activity, false);
+ window.setReturnTransition(backward);
+ window.setReenterTransition(backward);
+ }
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index 9130662..17f257d 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,9 +72,9 @@
private void init(Context context) {
setLayoutResource(R.layout.preference_two_target);
mSmallIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.two_target_pref_small_icon_size);
+ resourceId(context, "dimen", "two_target_pref_small_icon_size"));
mMediumIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.two_target_pref_medium_icon_size);
+ resourceId(context, "dimen", "two_target_pref_medium_icon_size"));
final int secondTargetResId = getSecondTargetResId();
if (secondTargetResId != 0) {
setWidgetLayoutResource(secondTargetResId);
@@ -116,4 +116,8 @@
protected int getSecondTargetResId() {
return 0;
}
+
+ private int resourceId(Context context, String type, String name) {
+ return context.getResources().getIdentifier(name, type, context.getPackageName());
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index dfde3c7..c61f8a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -250,6 +250,7 @@
* @param callback Callbacks triggered when recovery status changes.
*/
public void triggerSubsystemRestart(String reason, @NonNull RecoveryStatusCallback callback) {
+ // TODO: b/183530649 : clean-up or make use of the `reason` argument
mHandler.post(() -> {
boolean someSubsystemRestarted = false;
@@ -264,7 +265,7 @@
}
if (isWifiEnabled()) {
- mWifiManager.restartWifiSubsystem(reason);
+ mWifiManager.restartWifiSubsystem();
mWifiRestartInProgress = true;
someSubsystemRestarted = true;
startTrackingWifiRestart();
diff --git a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 1474f18..f62ca32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -86,6 +86,15 @@
}
}
+ @Override
+ protected void onDeveloperOptionsSwitchEnabled() {
+ super.onDeveloperOptionsSwitchEnabled();
+ if (isAvailable()) {
+ mPreference.setDisabledByAdmin(
+ checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+ }
+ }
+
public void enablePreference(boolean enabled) {
if (isAvailable()) {
mPreference.setEnabled(enabled);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 068efac..3877b1e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -749,7 +749,8 @@
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
Settings.Secure.SUPPRESS_DOZE,
Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED,
- Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
+ Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT,
+ Settings.Secure.TRANSFORM_ENABLED);
@Test
public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
index beee03b..caa9b4f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/DetailAdapter.java
@@ -16,6 +16,7 @@
import android.content.Context;
import android.content.Intent;
+import android.content.res.Resources;
import android.view.View;
import android.view.ViewGroup;
@@ -34,7 +35,22 @@
}
View createDetailView(Context context, View convertView, ViewGroup parent);
+
+ /**
+ * @return intent for opening more settings related to this detail panel. If null, the more
+ * settings button will not be shown
+ */
Intent getSettingsIntent();
+
+ /**
+ * @return resource id of the string to use for opening the settings intent. If
+ * {@code Resources.ID_NULL}, then use the default string:
+ * {@code com.android.systemui.R.string.quick_settings_more_settings}
+ */
+ default int getSettingsText() {
+ return Resources.ID_NULL;
+ }
+
void setToggleState(boolean state);
int getMetricsCategory();
diff --git a/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml
new file mode 100644
index 0000000..e49fc15
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:paddingMode="stack">
+ <item android:id="@android:id/background"
+ android:gravity="center_vertical|fill_horizontal">
+ <layer-list>
+ <item android:id="@+id/volume_seekbar_background_solid">
+ <shape>
+ <size android:height="@dimen/volume_dialog_slider_width" />
+ <solid android:color="@color/tv_volume_dialog_seek_bar_background"/>
+ <corners android:radius="@dimen/volume_dialog_slider_corner_radius" />
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+ <item android:id="@android:id/progress"
+ android:gravity="center_vertical|fill_horizontal">
+ <com.android.systemui.util.RoundedCornerProgressDrawable
+ android:drawable="@drawable/volume_row_seekbar_progress"
+ />
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml
new file mode 100644
index 0000000..bce193a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-television/volume_row_seekbar_progress.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<!-- Progress drawable for volume row SeekBars. This is the accent-colored round rect that moves up
+ and down as the progress value changes. -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
+ <item android:id="@+id/volume_seekbar_progress_solid">
+ <shape android:shape="rectangle">
+ <size android:height="@dimen/volume_dialog_slider_width"/>
+ <solid android:color="@color/tv_volume_dialog_seek_bar_fill" />
+ <corners android:radius="@dimen/volume_dialog_slider_width" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml b/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
deleted file mode 100644
index fe76b63..0000000
--- a/packages/SystemUI/res/drawable/tv_volume_row_seek_bar.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2020 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.
--->
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@android:id/background">
- <shape android:shape="rectangle">
- <solid android:color="@color/tv_volume_dialog_seek_bar_background" />
- <corners android:radius="@dimen/tv_volume_seek_bar_width" />
- </shape>
- </item>
- <item android:id="@android:id/progress">
- <clip>
- <shape android:shape="rectangle">
- <solid android:color="@color/tv_volume_dialog_seek_bar_fill" />
- <corners android:radius="@dimen/tv_volume_seek_bar_width" />
- </shape>
- </clip>
- </item>
-</layer-list>
diff --git a/packages/SystemUI/res/drawable/volume_row_seekbar.xml b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
index b0e0ed5..a845e73 100644
--- a/packages/SystemUI/res/drawable/volume_row_seekbar.xml
+++ b/packages/SystemUI/res/drawable/volume_row_seekbar.xml
@@ -25,9 +25,9 @@
<layer-list>
<item android:id="@+id/volume_seekbar_background_solid">
<shape>
- <size android:height="@dimen/volume_dialog_panel_width" />
+ <size android:height="@dimen/volume_dialog_slider_width" />
<solid android:color="?android:attr/colorBackgroundFloating" />
- <corners android:radius="@dimen/volume_dialog_panel_width_half" />
+ <corners android:radius="@dimen/volume_dialog_slider_corner_radius" />
</shape>
</item>
<item
@@ -53,4 +53,4 @@
android:drawable="@drawable/volume_row_seekbar_progress"
/>
</item>
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
index 4f6cb01..0dec981 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog_row.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:tag="row"
android:layout_width="wrap_content"
- android:layout_height="@dimen/volume_dialog_row_height"
+ android:layout_height="@dimen/volume_dialog_panel_height"
android:background="@android:color/transparent"
android:clipChildren="false"
android:clipToPadding="false"
@@ -54,18 +54,18 @@
<FrameLayout
android:id="@+id/volume_row_slider_frame"
android:layout_width="match_parent"
- android:layout_height="@dimen/volume_dialog_row_height">
+ android:layout_height="@dimen/volume_dialog_panel_height">
<SeekBar
android:id="@+id/volume_row_slider"
android:clickable="false"
- android:layout_width="@dimen/volume_dialog_row_height"
+ android:layout_width="@dimen/volume_dialog_panel_height"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layoutDirection="ltr"
- android:maxHeight="@dimen/tv_volume_seek_bar_width"
- android:minHeight="@dimen/tv_volume_seek_bar_width"
+ android:maxHeight="@dimen/volume_dialog_slider_width"
+ android:minHeight="@dimen/volume_dialog_slider_width"
+ android:progressDrawable="@drawable/volume_row_seekbar"
android:thumb="@drawable/tv_volume_row_seek_thumb"
- android:progressDrawable="@drawable/tv_volume_row_seek_bar"
android:splitTrack="false"
android:rotation="270" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_tile_large_with_content.xml b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
index f2341b5..9990244 100644
--- a/packages/SystemUI/res/layout/people_tile_large_with_content.xml
+++ b/packages/SystemUI/res/layout/people_tile_large_with_content.xml
@@ -22,27 +22,50 @@
android:padding="16dp"
android:orientation="vertical">
- <LinearLayout
- android:layout_width="wrap_content"
+ <RelativeLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:gravity="start|top"
- android:orientation="horizontal">
+ android:gravity="start|top">
- <ImageView
- android:id="@+id/person_icon"
- android:layout_marginStart="-2dp"
- android:layout_marginTop="-2dp"
+ <LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1" />
+ android:layout_alignParentStart="true"
+ android:gravity="start|top"
+ android:orientation="horizontal">
- <ImageView
- android:id="@+id/availability"
- android:layout_marginStart="-2dp"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp" />
- </LinearLayout>
+ <ImageView
+ android:id="@+id/person_icon"
+ android:layout_marginStart="-2dp"
+ android:layout_marginTop="-2dp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1" />
+
+ <ImageView
+ android:id="@+id/availability"
+ android:layout_marginStart="-2dp"
+ android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="@drawable/circle_green_10dp" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/messages_count"
+ android:layout_alignParentEnd="true"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+ </RelativeLayout>
<TextView
android:layout_gravity="center"
diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml
index 914ee3c..f0ab187 100644
--- a/packages/SystemUI/res/layout/people_tile_small.xml
+++ b/packages/SystemUI/res/layout/people_tile_small.xml
@@ -33,20 +33,36 @@
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_weight="1" />
+ android:layout_weight="1"
+ android:paddingBottom="4dp"/>
<ImageView
android:id="@+id/predefined_icon"
android:layout_gravity="center"
- android:paddingTop="4dp"
android:layout_width="18dp"
android:layout_height="22dp"
android:layout_weight="1" />
<TextView
+ android:id="@+id/messages_count"
+ android:layout_gravity="center"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="@drawable/people_space_messages_count_background"
+ android:textSize="14sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_weight="1"
+ />
+
+ <TextView
android:id="@+id/name"
android:layout_gravity="center"
- android:paddingTop="4dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index fda59b5..1d5bca9 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -54,6 +54,7 @@
android:layout_width="@dimen/volume_row_slider_height"
android:layout_height="match_parent"
android:layout_gravity="center"
+ android:thumb="@android:color/transparent"
android:rotation="270" />
</FrameLayout>
diff --git a/packages/SystemUI/res/values-land-television/dimens.xml b/packages/SystemUI/res/values-land-television/dimens.xml
index 95ff59b..8c90573 100644
--- a/packages/SystemUI/res/values-land-television/dimens.xml
+++ b/packages/SystemUI/res/values-land-television/dimens.xml
@@ -16,14 +16,17 @@
<resources>
<!-- Height of volume bar -->
- <dimen name="volume_dialog_row_height">190dp</dimen>
- <dimen name="volume_dialog_row_width">48dp</dimen>
+ <dimen name="volume_dialog_panel_height">190dp</dimen>
+ <dimen name="volume_dialog_panel_width">48dp</dimen>
+ <dimen name="volume_dialog_panel_width_half">24dp</dimen>
<dimen name="volume_dialog_panel_transparent_padding">24dp</dimen>
+ <dimen name="volume_dialog_slider_width">4dp</dimen>
+ <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_slider_width</dimen>
+
<dimen name="tv_volume_dialog_bubble_size">36dp</dimen>
<dimen name="tv_volume_dialog_corner_radius">36dp</dimen>
<dimen name="tv_volume_dialog_row_padding">6dp</dimen>
<dimen name="tv_volume_number_text_size">16sp</dimen>
- <dimen name="tv_volume_seek_bar_width">4dp</dimen>
<dimen name="tv_volume_seek_bar_thumb_diameter">24dp</dimen>
<dimen name="tv_volume_seek_bar_thumb_focus_ring_width">8dp</dimen>
<dimen name="tv_volume_icons_size">20dp</dimen>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index af6df32..fbe7175 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -591,4 +591,12 @@
<!-- Determines whether to allow the nav bar handle to be forced to be opaque. -->
<bool name="allow_force_nav_bar_handle_opaque">true</bool>
+
+ <!-- Whether a transition of ACTIVITY_TYPE_DREAM to the home app should play a home sound
+ effect -->
+ <bool name="config_playHomeSoundAfterDream">false</bool>
+
+ <!-- Whether a transition of ACTIVITY_TYPE_ASSISTANT to the home app should play a home sound
+ effect -->
+ <bool name="config_playHomeSoundAfterAssistant">false</bool>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2393b74..d5c6398 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -462,6 +462,10 @@
<dimen name="volume_dialog_slider_height">116dp</dimen>
+ <dimen name="volume_dialog_slider_width">@dimen/volume_dialog_panel_width</dimen>
+
+ <dimen name="volume_dialog_slider_corner_radius">@dimen/volume_dialog_panel_width_half</dimen>
+
<dimen name="volume_dialog_ringer_size">64dp</dimen>
<dimen name="volume_dialog_ringer_icon_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 94bf86a..5e70f18 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -239,10 +239,8 @@
<string name="screenshot_edit_label">Edit</string>
<!-- Content description indicating that tapping the element will allow editing the screenshot [CHAR LIMIT=NONE] -->
<string name="screenshot_edit_description">Edit screenshot</string>
- <!-- Label for UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=30] -->
- <string name="screenshot_scroll_label">Scroll</string>
- <!-- Content description UI element which allows scrolling and extending the screenshot to be taller [CHAR LIMIT=NONE] -->
- <string name="screenshot_scroll_description">Scroll screenshot</string>
+ <!-- Label for UI element which allows the user to capture additional off-screen content in a screenshot. [CHAR LIMIT=30] -->
+ <string name="screenshot_scroll_label">Capture more</string>
<!-- Content description indicating that tapping a button will dismiss the screenshots UI [CHAR LIMIT=NONE] -->
<string name="screenshot_dismiss_description">Dismiss screenshot</string>
<!-- Content description indicating that the view is a preview of the screenshot that was just taken [CHAR LIMIT=NONE] -->
@@ -889,6 +887,8 @@
<string name="quick_settings_color_space_label">Color correction mode</string>
<!-- QuickSettings: Control panel: Label for button that navigates to settings. [CHAR LIMIT=NONE] -->
<string name="quick_settings_more_settings">More settings</string>
+ <!-- QuickSettings: Control panel: Label for button that navigates to user settings. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_more_user_settings">User settings</string>
<!-- QuickSettings: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="quick_settings_done">Done</string>
<!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 72dd72e..02a8958 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -487,5 +487,9 @@
mView.setLayoutParams(lp);
}
}
+
+ if (mKeyguardSecurityContainerController != null) {
+ mKeyguardSecurityContainerController.updateResources();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 4887767..708b2d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -33,7 +33,6 @@
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
-import android.view.OrientationEventListener;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -107,7 +106,6 @@
private boolean mOneHandedMode = false;
private SecurityMode mSecurityMode = SecurityMode.Invalid;
private ViewPropertyAnimator mRunningOneHandedAnimator;
- private final OrientationEventListener mOrientationEventListener;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -247,13 +245,6 @@
super(context, attrs, defStyle);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mViewConfiguration = ViewConfiguration.get(context);
-
- mOrientationEventListener = new OrientationEventListener(context) {
- @Override
- public void onOrientationChanged(int orientation) {
- updateLayoutForSecurityMode(mSecurityMode);
- }
- };
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -262,7 +253,6 @@
updateBiometricRetry(securityMode, faceAuthEnabled);
updateLayoutForSecurityMode(securityMode);
- mOrientationEventListener.enable();
}
void updateLayoutForSecurityMode(SecurityMode securityMode) {
@@ -385,7 +375,6 @@
mAlertDialog = null;
}
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
- mOrientationEventListener.disable();
}
@Override
@@ -663,6 +652,15 @@
childState << MEASURED_HEIGHT_STATE_SHIFT));
}
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // After a layout pass, we need to re-place the inner bouncer, as our bounds may have
+ // changed.
+ updateSecurityViewLocation(/* animate= */false);
+ }
+
void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
String message = null;
switch (userType) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index ccba1d5..760eaec 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -29,6 +29,7 @@
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.util.Log;
@@ -74,6 +75,8 @@
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
+ private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private final Gefingerpoken mGlobalTouchListener = new Gefingerpoken() {
@@ -212,6 +215,7 @@
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
mConfigurationController = configurationController;
+ mLastOrientation = getResources().getConfiguration().orientation;
}
@Override
@@ -498,6 +502,19 @@
return getCurrentSecurityController();
}
+ /**
+ * Apply keyguard configuration from the currently active resources. This can be called when the
+ * device configuration changes, to re-apply some resources that are qualified on the device
+ * configuration.
+ */
+ public void updateResources() {
+ int newOrientation = getResources().getConfiguration().orientation;
+ if (newOrientation != mLastOrientation) {
+ mLastOrientation = newOrientation;
+ mView.updateLayoutForSecurityMode(mCurrentSecurityMode);
+ }
+ }
+
static class Factory {
private final KeyguardSecurityContainer mView;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index b4858f4..fd0c4ef 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -43,16 +43,16 @@
private final List<AccessibilityTarget> mTargets;
@IntDef({
- AccessibilityTargetAdapter.FIRST_ITEM,
- AccessibilityTargetAdapter.REGULAR_ITEM,
- AccessibilityTargetAdapter.LAST_ITEM
+ ItemType.FIRST_ITEM,
+ ItemType.REGULAR_ITEM,
+ ItemType.LAST_ITEM
})
@Retention(RetentionPolicy.SOURCE)
- @interface ItemType {}
-
- private static final int FIRST_ITEM = 0;
- private static final int REGULAR_ITEM = 1;
- private static final int LAST_ITEM = 2;
+ @interface ItemType {
+ int FIRST_ITEM = 0;
+ int REGULAR_ITEM = 1;
+ int LAST_ITEM = 2;
+ }
public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
mTargets = targets;
@@ -65,11 +65,11 @@
R.layout.accessibility_floating_menu_item, parent,
/* attachToRoot= */ false);
- if (itemType == FIRST_ITEM) {
+ if (itemType == ItemType.FIRST_ITEM) {
return new TopViewHolder(root);
}
- if (itemType == LAST_ITEM) {
+ if (itemType == ItemType.LAST_ITEM) {
return new BottomViewHolder(root);
}
@@ -87,14 +87,14 @@
@Override
public int getItemViewType(int position) {
if (position == 0) {
- return FIRST_ITEM;
+ return ItemType.FIRST_ITEM;
}
if (position == (getItemCount() - 1)) {
- return LAST_ITEM;
+ return ItemType.LAST_ITEM;
}
- return REGULAR_ITEM;
+ return ItemType.REGULAR_ITEM;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 7e7cdce..6812f77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -46,7 +46,6 @@
import java.util.Set;
import java.util.StringJoiner;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Named;
@@ -107,11 +106,14 @@
}
};
- private final FalsingDataProvider.GestureCompleteListener mGestureCompleteListener =
- new FalsingDataProvider.GestureCompleteListener() {
+ private final FalsingDataProvider.GestureFinalizedListener mGestureFinalizedListener =
+ new FalsingDataProvider.GestureFinalizedListener() {
@Override
- public void onGestureComplete(long completionTimeMs) {
+ public void onGestureFinalized(long completionTimeMs) {
if (mPriorResults != null) {
+ boolean boolResult = mPriorResults.stream().anyMatch(
+ FalsingClassifier.Result::isFalse);
+
mPriorResults.forEach(result -> {
if (result.isFalse()) {
String reason = result.getReason();
@@ -121,8 +123,28 @@
}
});
+ if (Build.IS_ENG || Build.IS_USERDEBUG) {
+ // Copy motion events, as the results returned by
+ // #getRecentMotionEvents are recycled elsewhere.
+ RECENT_SWIPES.add(new DebugSwipeRecord(
+ boolResult,
+ mPriorInteractionType,
+ mDataProvider.getRecentMotionEvents().stream().map(
+ motionEvent -> new XYDt(
+ (int) motionEvent.getX(),
+ (int) motionEvent.getY(),
+ (int) (motionEvent.getEventTime()
+ - motionEvent.getDownTime())))
+ .collect(Collectors.toList())));
+ while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) {
+ RECENT_SWIPES.remove();
+ }
+ }
+
+
mHistoryTracker.addResults(mPriorResults, completionTimeMs);
mPriorResults = null;
+ mPriorInteractionType = Classifier.GENERIC;
} else {
// Gestures that were not classified get treated as a false.
mHistoryTracker.addResults(
@@ -135,6 +157,7 @@
};
private Collection<FalsingClassifier.Result> mPriorResults;
+ private @Classifier.InteractionType int mPriorInteractionType = Classifier.GENERIC;
@Inject
public BrightLineFalsingManager(FalsingDataProvider falsingDataProvider,
@@ -154,7 +177,7 @@
mTestHarness = testHarness;
mDataProvider.addSessionListener(mSessionListener);
- mDataProvider.addGestureCompleteListener(mGestureCompleteListener);
+ mDataProvider.addGestureCompleteListener(mGestureFinalizedListener);
mHistoryTracker.addBeliefListener(mBeliefListener);
}
@@ -165,50 +188,33 @@
@Override
public boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+ mPriorInteractionType = interactionType;
if (skipFalsing()) {
return false;
}
- boolean result;
+ final boolean booleanResult;
if (!mTestHarness && !mDataProvider.isJustUnlockedWithFace() && !mDockManager.isDocked()) {
- Stream<FalsingClassifier.Result> results =
- mClassifiers.stream().map(falsingClassifier ->
- falsingClassifier.classifyGesture(
- interactionType,
- mHistoryTracker.falseBelief(),
- mHistoryTracker.falseConfidence()));
- mPriorResults = new ArrayList<>();
final boolean[] localResult = {false};
- results.forEach(classifierResult -> {
- localResult[0] |= classifierResult.isFalse();
- mPriorResults.add(classifierResult);
- });
- result = localResult[0];
+ mPriorResults = mClassifiers.stream().map(falsingClassifier -> {
+ FalsingClassifier.Result r = falsingClassifier.classifyGesture(
+ interactionType,
+ mHistoryTracker.falseBelief(),
+ mHistoryTracker.falseConfidence());
+ localResult[0] |= r.isFalse();
+
+ return r;
+ }).collect(Collectors.toList());
+ booleanResult = localResult[0];
} else {
- result = false;
+ booleanResult = false;
mPriorResults = Collections.singleton(FalsingClassifier.Result.passed(1));
}
- logDebug("False Gesture: " + result);
+ logDebug("False Gesture: " + booleanResult);
- if (Build.IS_ENG || Build.IS_USERDEBUG) {
- // Copy motion events, as the passed in list gets emptied out elsewhere in the code.
- RECENT_SWIPES.add(new DebugSwipeRecord(
- result,
- interactionType,
- mDataProvider.getRecentMotionEvents().stream().map(
- motionEvent -> new XYDt(
- (int) motionEvent.getX(),
- (int) motionEvent.getY(),
- (int) (motionEvent.getEventTime() - motionEvent.getDownTime())))
- .collect(Collectors.toList())));
- while (RECENT_SWIPES.size() > RECENT_INFO_LOG_SIZE) {
- RECENT_SWIPES.remove();
- }
- }
-
- return result;
+ return booleanResult;
}
@Override
@@ -354,7 +360,7 @@
@Override
public void cleanup() {
mDataProvider.removeSessionListener(mSessionListener);
- mDataProvider.removeGestureCompleteListener(mGestureCompleteListener);
+ mDataProvider.removeGestureCompleteListener(mGestureFinalizedListener);
mClassifiers.forEach(FalsingClassifier::cleanup);
mFalsingBeliefListeners.clear();
mHistoryTracker.removeBeliefListener(mBeliefListener);
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index f665a74..1aaa139 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -45,7 +45,7 @@
private final float mYdpi;
private final List<SessionListener> mSessionListeners = new ArrayList<>();
private final List<MotionEventListener> mMotionEventListeners = new ArrayList<>();
- private final List<GestureCompleteListener> mGestureCompleteListeners = new ArrayList<>();
+ private final List<GestureFinalizedListener> mGestureFinalizedListeners = new ArrayList<>();
private TimeLimitedMotionEventBuffer mRecentMotionEvents =
new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
@@ -90,7 +90,7 @@
mMotionEventListeners.forEach(listener -> listener.onMotionEvent(motionEvent));
- // We explicitly do not complete a gesture on UP or CANCEL events.
+ // We explicitly do not "finalize" a gesture on UP or CANCEL events.
// We wait for the next gesture to start before marking the prior gesture as complete. This
// has multiple benefits. First, it makes it trivial to track the "current" or "recent"
// gesture, as it will always be found in mRecentMotionEvents. Second, and most importantly,
@@ -102,7 +102,7 @@
private void completePriorGesture() {
if (!mRecentMotionEvents.isEmpty()) {
- mGestureCompleteListeners.forEach(listener -> listener.onGestureComplete(
+ mGestureFinalizedListeners.forEach(listener -> listener.onGestureFinalized(
mRecentMotionEvents.get(mRecentMotionEvents.size() - 1).getEventTime()));
mPriorMotionEvents = mRecentMotionEvents;
@@ -312,14 +312,14 @@
mMotionEventListeners.remove(listener);
}
- /** Register a {@link GestureCompleteListener}. */
- public void addGestureCompleteListener(GestureCompleteListener listener) {
- mGestureCompleteListeners.add(listener);
+ /** Register a {@link GestureFinalizedListener}. */
+ public void addGestureCompleteListener(GestureFinalizedListener listener) {
+ mGestureFinalizedListeners.add(listener);
}
- /** Unregister a {@link GestureCompleteListener}. */
- public void removeGestureCompleteListener(GestureCompleteListener listener) {
- mGestureCompleteListeners.remove(listener);
+ /** Unregister a {@link GestureFinalizedListener}. */
+ public void removeGestureCompleteListener(GestureFinalizedListener listener) {
+ mGestureFinalizedListeners.remove(listener);
}
void onSessionStarted() {
@@ -362,8 +362,12 @@
}
/** Callback to be alerted when the current gesture ends. */
- public interface GestureCompleteListener {
- /** */
- void onGestureComplete(long completionTimeMs);
+ public interface GestureFinalizedListener {
+ /**
+ * Called just before a new gesture starts.
+ *
+ * Any pending work on a prior gesture can be considered cemented in place.
+ */
+ void onGestureFinalized(long completionTimeMs);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
index dd3d02a..31e4939 100644
--- a/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/systemsounds/HomeSoundEffectController.java
@@ -16,38 +16,64 @@
package com.android.systemui.media.systemsounds;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
+import android.util.Slog;
+import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import javax.inject.Inject;
/**
- * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a sound is played
- * when the home task moves to front and the last task that moved to front was not the home task.
+ * If a sound effect is defined for {@link android.media.AudioManager#FX_HOME}, a
+ * {@link TaskStackChangeListener} is registered to play a home sound effect when conditions
+ * documented at {@link #handleTaskStackChanged} apply.
*/
@SysUISingleton
public class HomeSoundEffectController extends SystemUI {
+ private static final String TAG = "HomeSoundEffectController";
private final AudioManager mAudioManager;
private final TaskStackChangeListeners mTaskStackChangeListeners;
+ private final ActivityManagerWrapper mActivityManagerWrapper;
+ private final PackageManager mPm;
+ private final boolean mPlayHomeSoundAfterAssistant;
+ private final boolean mPlayHomeSoundAfterDream;
// Initialize true because home sound should not be played when the system boots.
private boolean mIsLastTaskHome = true;
+ // mLastHomePackageName could go out of sync in rare circumstances if launcher changes,
+ // but it's cheaper than the alternative and potential impact is low
+ private String mLastHomePackageName;
+ private @WindowConfiguration.ActivityType int mLastActivityType;
+ private boolean mLastActivityHasNoHomeSound = false;
+ private int mLastTaskId;
@Inject
public HomeSoundEffectController(
Context context,
AudioManager audioManager,
- TaskStackChangeListeners taskStackChangeListeners) {
+ TaskStackChangeListeners taskStackChangeListeners,
+ ActivityManagerWrapper activityManagerWrapper,
+ PackageManager packageManager) {
super(context);
mAudioManager = audioManager;
mTaskStackChangeListeners = taskStackChangeListeners;
+ mActivityManagerWrapper = activityManagerWrapper;
+ mPm = packageManager;
+ mPlayHomeSoundAfterAssistant = context.getResources().getBoolean(
+ R.bool.config_playHomeSoundAfterAssistant);
+ mPlayHomeSoundAfterDream = context.getResources().getBoolean(
+ R.bool.config_playHomeSoundAfterDream);
}
@Override
@@ -56,27 +82,94 @@
mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- handleHomeTaskMovedToFront(taskInfo);
+ public void onTaskStackChanged() {
+ ActivityManager.RunningTaskInfo currentTask =
+ mActivityManagerWrapper.getRunningTask();
+ if (currentTask == null || currentTask.topActivityInfo == null) {
+ return;
+ }
+ handleTaskStackChanged(currentTask);
}
});
}
}
- private boolean isHomeTask(ActivityManager.RunningTaskInfo taskInfo) {
- return taskInfo.getActivityType() == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ private boolean hasFlagNoSound(ActivityInfo activityInfo) {
+ if ((activityInfo.privateFlags & ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND) == 0) {
+ // Only allow flag if app has permission
+ if (mPm.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ activityInfo.packageName) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else {
+ Slog.w(TAG,
+ "Activity has flag playHomeTransition set to false but doesn't hold "
+ + "required permission "
+ + Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS);
+ return false;
+ }
+ }
+ return false;
}
/**
- * To enable a home sound, check if the home app moves to front.
+ * The home sound is played if all of the following conditions are met:
+ * <ul>
+ * <li>The last task which moved to front was not home. This avoids playing the sound
+ * e.g. after FallbackHome transitions to home, another activity of the home app like a
+ * notification panel moved to front, or in case the home app crashed.</li>
+ * <li>The current activity which moved to front is home</li>
+ * <li>The topActivity of the last task has {@link android.R.attr#playHomeTransitionSound} set
+ * to <code>true</code>.</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_ASSISTANT} if config_playHomeSoundAfterAssistant is
+ * set to <code>false</code> (default).</li>
+ * <li>The topActivity of the last task is not of type
+ * {@link WindowConfiguration#ACTIVITY_TYPE_DREAM} if config_playHomeSoundAfterDream is
+ * set to <code>false</code> (default).</li>
+ * </ul>
*/
- private void handleHomeTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
- boolean isCurrentTaskHome = isHomeTask(taskInfo);
- // If the last task is home we don't want to play the home sound. This avoids playing
- // the home sound after FallbackHome transitions to Home
- if (!mIsLastTaskHome && isCurrentTaskHome) {
+ private boolean shouldPlayHomeSoundForCurrentTransition(
+ ActivityManager.RunningTaskInfo currentTask) {
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ if (currentTask.taskId == mLastTaskId) {
+ return false;
+ }
+ if (mIsLastTaskHome || !isHomeActivity) {
+ return false;
+ }
+ if (mLastActivityHasNoHomeSound) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+ && !mPlayHomeSoundAfterAssistant) {
+ return false;
+ }
+ if (mLastActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ && !mPlayHomeSoundAfterDream) {
+ return false;
+ }
+ return true;
+ }
+
+ private void updateLastTaskInfo(ActivityManager.RunningTaskInfo currentTask) {
+ mLastTaskId = currentTask.taskId;
+ mLastActivityType = currentTask.topActivityType;
+ mLastActivityHasNoHomeSound = hasFlagNoSound(currentTask.topActivityInfo);
+ boolean isHomeActivity =
+ currentTask.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME;
+ boolean isHomePackage = currentTask.topActivityInfo.packageName.equals(
+ mLastHomePackageName);
+ mIsLastTaskHome = isHomeActivity || isHomePackage;
+ if (isHomeActivity && !isHomePackage) {
+ mLastHomePackageName = currentTask.topActivityInfo.packageName;
+ }
+ }
+
+ private void handleTaskStackChanged(ActivityManager.RunningTaskInfo frontTask) {
+ if (shouldPlayHomeSoundForCurrentTransition(frontTask)) {
mAudioManager.playSoundEffect(AudioManager.FX_HOME);
}
- mIsLastTaskHome = isCurrentTaskHome;
+ updateLastTaskInfo(frontTask);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index bc196bf..8d1b712 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -361,10 +361,13 @@
views.setViewVisibility(R.id.image, View.GONE);
views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message);
}
- if (mTile.getMessagesCount() > 1 && mLayoutSize == LAYOUT_MEDIUM) {
+ if (mTile.getMessagesCount() > 1) {
views.setViewVisibility(R.id.messages_count, View.VISIBLE);
views.setTextViewText(R.id.messages_count,
getMessagesCountText(mTile.getMessagesCount()));
+ if (mLayoutSize == LAYOUT_SMALL) {
+ views.setViewVisibility(R.id.predefined_icon, View.GONE);
+ }
}
// TODO: Set subtext as Group Sender name once storing the name in PeopleSpaceTile and
// subtract 1 from maxLines when present.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 9967936..05bc6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -140,7 +141,10 @@
private void updateDetailText() {
mDetailDoneButton.setText(R.string.quick_settings_done);
- mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
+ final int resId =
+ mDetailAdapter != null ? mDetailAdapter.getSettingsText() : Resources.ID_NULL;
+ mDetailSettingsButton.setText(
+ (resId != Resources.ID_NULL) ? resId : R.string.quick_settings_more_settings);
}
public void updateResources() {
@@ -218,6 +222,7 @@
mQsPanelController.setGridContentVisibility(true);
mQsPanelCallback.onScanStateChanged(false);
}
+ updateDetailText();
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
if (mShouldAnimate) {
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/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index ead6d32..e27c1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -189,7 +189,7 @@
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
val zoomOut = blurUtils.ratioOfBlurRadius(blur)
try {
- if (root.isAttachedToWindow) {
+ if (root.isAttachedToWindow && root.windowToken != null) {
wallpaperManager.setWallpaperZoomOut(root.windowToken, zoomOut)
} else {
Log.i(TAG, "Won't set zoom. Window not attached $root")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0da441d..83558cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -822,6 +822,11 @@
}
@Override
+ public int getSettingsText() {
+ return R.string.quick_settings_more_user_settings;
+ }
+
+ @Override
public Boolean getToggleState() {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 5dc7006..044d828 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -546,9 +546,10 @@
row.sliderBgSolid = seekbarBgDrawable.findDrawableByLayerId(
R.id.volume_seekbar_background_solid);
- row.sliderBgIcon = (AlphaTintDrawableWrapper)
- ((RotateDrawable) seekbarBgDrawable.findDrawableByLayerId(
- R.id.volume_seekbar_background_icon)).getDrawable();
+ final Drawable sliderBgIcon = seekbarBgDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_background_icon);
+ row.sliderBgIcon = sliderBgIcon != null ? (AlphaTintDrawableWrapper)
+ ((RotateDrawable) sliderBgIcon).getDrawable() : null;
final LayerDrawable seekbarProgressDrawable = (LayerDrawable)
((RoundedCornerProgressDrawable) seekbarDrawable.findDrawableByLayerId(
@@ -556,13 +557,12 @@
row.sliderProgressSolid = seekbarProgressDrawable.findDrawableByLayerId(
R.id.volume_seekbar_progress_solid);
-
- row.sliderProgressIcon = (AlphaTintDrawableWrapper)
- ((RotateDrawable) seekbarProgressDrawable.findDrawableByLayerId(
- R.id.volume_seekbar_progress_icon)).getDrawable();
+ final Drawable sliderProgressIcon = seekbarProgressDrawable.findDrawableByLayerId(
+ R.id.volume_seekbar_progress_icon);
+ row.sliderProgressIcon = sliderProgressIcon != null ? (AlphaTintDrawableWrapper)
+ ((RotateDrawable) sliderProgressIcon).getDrawable() : null;
row.slider.setProgressDrawable(seekbarDrawable);
- row.slider.setThumb(null);
row.icon = row.view.findViewById(R.id.volume_row_icon);
@@ -1484,10 +1484,14 @@
mContext, android.R.attr.colorBackgroundFloating);
row.sliderProgressSolid.setTintList(colorTint);
- row.sliderBgIcon.setTintList(colorTint);
+ if (row.sliderBgIcon != null) {
+ row.sliderBgIcon.setTintList(colorTint);
+ }
row.sliderBgSolid.setTintList(bgTint);
- row.sliderProgressIcon.setTintList(bgTint);
+ if (row.sliderProgressIcon != null) {
+ row.sliderProgressIcon.setTintList(bgTint);
+ }
if (row.icon != null) {
row.icon.setImageTintList(colorTint);
@@ -1878,8 +1882,12 @@
icon.setImageResource(iconRes);
}
- sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
- sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ if (sliderProgressIcon != null) {
+ sliderProgressIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ }
+ if (sliderBgIcon != null) {
+ sliderBgIcon.setDrawable(view.getResources().getDrawable(iconRes, theme));
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index ddfa63a..709ccd4 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -16,26 +16,18 @@
package com.android.systemui.wmshell;
-import static android.os.Process.THREAD_PRIORITY_DISPLAY;
-import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-
-import android.animation.AnimationHandler;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.os.HandlerThread;
import android.view.IWindowManager;
import android.view.WindowManager;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.R;
import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.FullscreenTaskListener;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler;
@@ -53,13 +45,11 @@
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
@@ -99,110 +89,9 @@
* dependencies that are device/form factor SystemUI implementation specific should go into their
* respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
*/
-@Module
+@Module(includes = WMShellConcurrencyModule.class)
public abstract class WMShellBaseModule {
- /**
- * Returns whether to enable a separate shell thread for the shell features.
- */
- private static boolean enableShellMainThread(Context context) {
- return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
- }
-
- //
- // Shell Concurrency - Components used for managing threading in the Shell and SysUI
- //
-
- /**
- * Provide a SysUI main-thread Executor.
- */
- @WMSingleton
- @Provides
- @Main
- public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) {
- return new HandlerExecutor(sysuiMainHandler);
- }
-
- /**
- * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
- * multiple types of messages, etc.)
- */
- @WMSingleton
- @Provides
- @ShellMainThread
- public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) {
- if (enableShellMainThread(context)) {
- HandlerThread mainThread = new HandlerThread("wmshell.main");
- mainThread.start();
- return mainThread.getThreadHandler();
- }
- return sysuiMainHandler;
- }
-
- /**
- * Provide a Shell main-thread Executor.
- */
- @WMSingleton
- @Provides
- @ShellMainThread
- public static ShellExecutor provideShellMainExecutor(Context context,
- @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) {
- if (enableShellMainThread(context)) {
- return new HandlerExecutor(mainHandler);
- }
- return sysuiMainExecutor;
- }
-
- /**
- * Provide a Shell animation-thread Executor.
- */
- @WMSingleton
- @Provides
- @ShellAnimationThread
- public static ShellExecutor provideShellAnimationExecutor() {
- HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim",
- THREAD_PRIORITY_DISPLAY);
- shellAnimationThread.start();
- return new HandlerExecutor(shellAnimationThread.getThreadHandler());
- }
-
- /**
- * Provides a Shell splashscreen-thread Executor
- */
- @WMSingleton
- @Provides
- @ShellSplashscreenThread
- public static ShellExecutor provideSplashScreenExecutor() {
- HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen",
- THREAD_PRIORITY_TOP_APP_BOOST);
- shellSplashscreenThread.start();
- return new HandlerExecutor(shellSplashscreenThread.getThreadHandler());
- }
-
- /**
- * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
- * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
- * the Shell main-thread with the SF vsync.
- */
- @WMSingleton
- @Provides
- @ChoreographerSfVsync
- public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
- @ShellMainThread ShellExecutor mainExecutor) {
- try {
- AnimationHandler handler = new AnimationHandler();
- mainExecutor.executeBlocking(() -> {
- // This is called on the animation thread since it calls
- // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
- // that uses the SF vsync
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- });
- return handler;
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
- }
- }
-
//
// Internal common - Components used internally by multiple shell features
//
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java
new file mode 100644
index 0000000..61f50b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellConcurrencyModule.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 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.systemui.wmshell;
+
+import static android.os.Process.THREAD_PRIORITY_DISPLAY;
+import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
+
+import android.animation.AnimationHandler;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+
+import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.R;
+import com.android.systemui.dagger.WMSingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
+import com.android.wm.shell.common.annotations.ShellAnimationThread;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Provides basic concurrency-related dependencies from {@link com.android.wm.shell}, these
+ * dependencies are only accessible from components within the WM subcomponent.
+ */
+@Module
+public abstract class WMShellConcurrencyModule {
+
+ private static final int MSGQ_SLOW_DELIVERY_THRESHOLD_MS = 30;
+ private static final int MSGQ_SLOW_DISPATCH_THRESHOLD_MS = 30;
+
+ /**
+ * Returns whether to enable a separate shell thread for the shell features.
+ */
+ private static boolean enableShellMainThread(Context context) {
+ return context.getResources().getBoolean(R.bool.config_enableShellMainThread);
+ }
+
+ //
+ // Shell Concurrency - Components used for managing threading in the Shell and SysUI
+ //
+
+ /**
+ * Provide a SysUI main-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @Main
+ public static ShellExecutor provideSysUIMainExecutor(@Main Handler sysuiMainHandler) {
+ return new HandlerExecutor(sysuiMainHandler);
+ }
+
+ /**
+ * Shell main-thread Handler, don't use this unless really necessary (ie. need to dedupe
+ * multiple types of messages, etc.)
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static Handler provideShellMainHandler(Context context, @Main Handler sysuiMainHandler) {
+ if (enableShellMainThread(context)) {
+ HandlerThread mainThread = new HandlerThread("wmshell.main", THREAD_PRIORITY_DISPLAY);
+ mainThread.start();
+ if (Build.IS_DEBUGGABLE) {
+ mainThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ mainThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return Handler.createAsync(mainThread.getLooper());
+ }
+ return sysuiMainHandler;
+ }
+
+ /**
+ * Provide a Shell main-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static ShellExecutor provideShellMainExecutor(Context context,
+ @ShellMainThread Handler mainHandler, @Main ShellExecutor sysuiMainExecutor) {
+ if (enableShellMainThread(context)) {
+ return new HandlerExecutor(mainHandler);
+ }
+ return sysuiMainExecutor;
+ }
+
+ /**
+ * Provide a Shell animation-thread Executor.
+ */
+ @WMSingleton
+ @Provides
+ @ShellAnimationThread
+ public static ShellExecutor provideShellAnimationExecutor() {
+ HandlerThread shellAnimationThread = new HandlerThread("wmshell.anim",
+ THREAD_PRIORITY_DISPLAY);
+ shellAnimationThread.start();
+ if (Build.IS_DEBUGGABLE) {
+ shellAnimationThread.getLooper().setTraceTag(Trace.TRACE_TAG_WINDOW_MANAGER);
+ shellAnimationThread.getLooper().setSlowLogThresholdMs(MSGQ_SLOW_DISPATCH_THRESHOLD_MS,
+ MSGQ_SLOW_DELIVERY_THRESHOLD_MS);
+ }
+ return new HandlerExecutor(Handler.createAsync(shellAnimationThread.getLooper()));
+ }
+
+ /**
+ * Provides a Shell splashscreen-thread Executor
+ */
+ @WMSingleton
+ @Provides
+ @ShellSplashscreenThread
+ public static ShellExecutor provideSplashScreenExecutor() {
+ HandlerThread shellSplashscreenThread = new HandlerThread("wmshell.splashscreen",
+ THREAD_PRIORITY_TOP_APP_BOOST);
+ shellSplashscreenThread.start();
+ return new HandlerExecutor(shellSplashscreenThread.getThreadHandler());
+ }
+
+ /**
+ * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
+ * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
+ * the Shell main-thread with the SF vsync.
+ */
+ @WMSingleton
+ @Provides
+ @ChoreographerSfVsync
+ public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
+ @ShellMainThread ShellExecutor mainExecutor) {
+ try {
+ AnimationHandler handler = new AnimationHandler();
+ mainExecutor.executeBlocking(() -> {
+ // This is called on the animation thread since it calls
+ // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
+ // that uses the SF vsync
+ handler.setProvider(new SfVsyncFrameCallbackProvider());
+ });
+ return handler;
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 084c0b4..99b3f6f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -25,9 +25,11 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -96,6 +98,9 @@
private ConfigurationController mConfigurationController;
@Mock
private EmergencyButtonController mEmergencyButtonController;
+ @Mock
+ private Resources mResources;
+ private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
private KeyguardPasswordViewController mKeyguardPasswordViewController;
@@ -103,6 +108,11 @@
@Before
public void setup() {
+ mConfiguration = new Configuration();
+ mConfiguration.setToDefaults(); // Defaults to ORIENTATION_UNDEFINED.
+
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
+ when(mView.getResources()).thenReturn(mResources);
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -154,4 +164,17 @@
verify(mWindowInsetsController).controlWindowInsetsAnimation(
eq(ime()), anyLong(), any(), any(), any());
}
+
+ @Test
+ public void onResourcesUpdate_callsThroughOnRotationChange() {
+ // Rotation is the same, shouldn't cause an update
+ mKeyguardSecurityContainerController.updateResources();
+ verify(mView, times(0)).updateLayoutForSecurityMode(any());
+
+ // Update rotation. Should trigger update
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+
+ mKeyguardSecurityContainerController.updateResources();
+ verify(mView, times(1)).updateLayoutForSecurityMode(any());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 67c1d07..1f165bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -34,7 +34,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.classifier.FalsingDataProvider.GestureCompleteListener;
+import com.android.systemui.classifier.FalsingDataProvider.GestureFinalizedListener;
import com.android.systemui.dock.DockManagerFake;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -82,7 +82,7 @@
private final FalsingClassifier.Result mFalsedResult =
FalsingClassifier.Result.falsed(1, getClass().getSimpleName(), "");
private final FalsingClassifier.Result mPassedResult = FalsingClassifier.Result.passed(1);
- private GestureCompleteListener mGestureCompleteListener;
+ private GestureFinalizedListener mGestureFinalizedListener;
@Before
public void setup() {
@@ -103,13 +103,13 @@
mHistoryTracker, mKeyguardStateController, false);
- ArgumentCaptor<GestureCompleteListener> gestureCompleteListenerCaptor =
- ArgumentCaptor.forClass(GestureCompleteListener.class);
+ ArgumentCaptor<GestureFinalizedListener> gestureCompleteListenerCaptor =
+ ArgumentCaptor.forClass(GestureFinalizedListener.class);
verify(mFalsingDataProvider).addGestureCompleteListener(
gestureCompleteListenerCaptor.capture());
- mGestureCompleteListener = gestureCompleteListenerCaptor.getValue();
+ mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
}
@Test
@@ -211,7 +211,7 @@
@Test
public void testHistory() {
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@@ -220,7 +220,7 @@
public void testHistory_singleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
}
@@ -228,9 +228,9 @@
public void testHistory_multipleSingleTaps() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(2000);
+ mGestureFinalizedListener.onGestureFinalized(2000);
verify(mHistoryTracker).addResults(anyCollection(), eq(1000L));
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
}
@@ -239,11 +239,11 @@
public void testHistory_doubleTap() {
// When trying to classify single taps, we don't immediately add results to history.
mBrightLineFalsingManager.isFalseTap(false, 0);
- mGestureCompleteListener.onGestureComplete(1000);
+ mGestureFinalizedListener.onGestureFinalized(1000);
// Before checking for double tap, we may check for single-tap on the second gesture.
mBrightLineFalsingManager.isFalseTap(false, 0);
mBrightLineFalsingManager.isFalseDoubleTap();
- mGestureCompleteListener.onGestureComplete(2000);
+ mGestureFinalizedListener.onGestureFinalized(2000);
// Double tap is immediately added to history. Single tap is never added.
verify(mHistoryTracker).addResults(anyCollection(), eq(2000L));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
index 3a77f7ee..33a30e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/systemsounds/HomeSoundEffectControllerTest.java
@@ -21,16 +21,20 @@
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.Manifest;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
-import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -41,30 +45,50 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class HomeSoundEffectControllerTest extends SysuiTestCase {
- private @Mock Context mContext;
+ private static final String HOME_PACKAGE_NAME = "com.android.apps.home";
+ private static final String NON_HOME_PACKAGE_NAME = "com.android.apps.not.home";
+ private static final int HOME_TASK_ID = 0;
+ private static final int NON_HOME_TASK_ID = 1;
+
private @Mock AudioManager mAudioManager;
private @Mock TaskStackChangeListeners mTaskStackChangeListeners;
- private @Mock ActivityManager.RunningTaskInfo mStandardActivityTaskInfo;
- private @Mock ActivityManager.RunningTaskInfo mHomeActivityTaskInfo;
+ private @Mock ActivityManagerWrapper mActivityManagerWrapper;
+ private @Mock PackageManager mPackageManager;
+ private ActivityManager.RunningTaskInfo mTaskAStandardActivity;
+ private ActivityManager.RunningTaskInfo mTaskAExceptionActivity;
+ private ActivityManager.RunningTaskInfo mHomeTaskHomeActivity;
+ private ActivityManager.RunningTaskInfo mHomeTaskStandardActivity;
+ private ActivityManager.RunningTaskInfo mEmptyTask;
private HomeSoundEffectController mController;
private TaskStackChangeListener mTaskStackChangeListener;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
-
- doReturn(WindowConfiguration.ACTIVITY_TYPE_STANDARD).when(
- mStandardActivityTaskInfo).getActivityType();
- doReturn(WindowConfiguration.ACTIVITY_TYPE_HOME).when(
- mHomeActivityTaskInfo).getActivityType();
-
+ mTaskAStandardActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mTaskAExceptionActivity = createRunningTaskInfo(NON_HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, NON_HOME_TASK_ID,
+ false /* playHomeTransitionSound */);
+ mHomeTaskHomeActivity = createRunningTaskInfo(HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_HOME, HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mHomeTaskStandardActivity = createRunningTaskInfo(HOME_PACKAGE_NAME,
+ WindowConfiguration.ACTIVITY_TYPE_STANDARD, HOME_TASK_ID,
+ true /* playHomeTransitionSound */);
+ mEmptyTask = new ActivityManager.RunningTaskInfo();
+ mContext.setMockPackageManager(mPackageManager);
+ when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_GRANTED);
mController = new HomeSoundEffectController(mContext, mAudioManager,
- mTaskStackChangeListeners);
+ mTaskStackChangeListeners, mActivityManagerWrapper, mPackageManager);
}
@Test
@@ -73,43 +97,60 @@
startController(true /* isHomeSoundEffectEnabled */);
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(mHomeTaskHomeActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
}
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME
+ * Expectation: Home sound is played
+ */
@Test
public void testHomeSoundEffectPlayedWhenEnabled() {
// When HomeSoundEffectController is started and the home sound effect is enabled,
startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
// And first a task different from the home task moves to front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then the home sound effect should be played.
verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
}
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME -> HOME
+ * Expectation: Home sound is played once after HOME moves to front the first time
+ */
@Test
public void testHomeSoundEffectNotPlayedTwiceInRow() {
// When HomeSoundEffectController is started and the home sound effect is enabled,
startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity,
+ mHomeTaskHomeActivity);
+
// And first a task different from the home task moves to front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// And the home task moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then the home sound effect should be played.
verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
// If the home task moves to front a second time in a row,
- mTaskStackChangeListener.onTaskMovedToFront(mHomeActivityTaskInfo);
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME);
@@ -121,7 +162,59 @@
startController(true /* isHomeSoundEffectEnabled */);
// And a standard, non-home task, moves to the front,
- mTaskStackChangeListener.onTaskMovedToFront(mStandardActivityTaskInfo);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(mTaskAStandardActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME -> HOME (activity type standard)
+ * Expectation: Home sound is played once after HOME moves to front
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFront() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity,
+ mHomeTaskStandardActivity);
+
+ // And first a task different from the home task moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+
+ // If the home task moves to front a second time in a row,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, times(1)).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * Task A (playHomeTransitionSound = true) -> HOME (activity type standard)
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenOtherHomeActivityMovesToFrontOfOtherApp() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front,
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskStandardActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And an activity from the home package but not the home root activity moves to front
+ mTaskStackChangeListener.onTaskStackChanged();
// Then no home sound effect should be played.
verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
@@ -138,6 +231,171 @@
}
/**
+ * Task A (playHomeTransitionSound = false) -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ // And first a task different from the home task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mHomeTaskHomeActivity);
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played because the last package is an exception.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = true) -> Task A (playHomeTransitionSound = false)
+ * -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenHomeActivityMovesToFrontAfterException2() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mTaskAExceptionActivity,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a different activity from the same task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false) -> Task A (playHomeTransitionSound = true)
+ * -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenHomeActivityMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front,
+ // the topActivity of this task has {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND}
+ // set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a different activity from the same task moves to front, which has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false) -> Task A (empty task, no top activity)
+ * -> HOME
+ * Expectation: Home sound is not played
+ */
+ @Test
+ public void testHomeSoundEffectNotPlayedWhenEmptyTaskMovesToFrontAfterException() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAExceptionActivity,
+ mEmptyTask,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a task with no topActivity moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then no home sound effect should be played.
+ verify(mAudioManager, never()).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = true) -> Task A (empty task, no top activity)
+ * -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenEmptyTaskMovesToFrontAfterStandardActivity() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mEmptyTask,
+ mHomeTaskHomeActivity);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>false</code>
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then a task with no topActivity moves to front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
+ * HOME -> Task A (playHomeTransitionSound = false, no permission) -> HOME
+ * Expectation: Home sound is played
+ */
+ @Test
+ public void testHomeSoundEffectPlayedWhenFlagSetButPermissionNotGranted() {
+ // When HomeSoundEffectController is started and the home sound effect is enabled,
+ startController(true /* isHomeSoundEffectEnabled */);
+ when(mActivityManagerWrapper.getRunningTask()).thenReturn(
+ mTaskAStandardActivity,
+ mHomeTaskHomeActivity);
+ when(mPackageManager.checkPermission(Manifest.permission.DISABLE_SYSTEM_SOUND_EFFECTS,
+ NON_HOME_PACKAGE_NAME)).thenReturn(PackageManager.PERMISSION_DENIED);
+
+ // And first a task different from the home task moves to front, whose topActivity has
+ // {@link ActivityInfo#PRIVATE_FLAG_HOME_TRANSITION_SOUND} set to <code>true</code>,
+ // but the app doesn't have the right permission granted
+ mTaskStackChangeListener.onTaskStackChanged();
+
+
+ // And the home task moves to the front,
+ mTaskStackChangeListener.onTaskStackChanged();
+
+ // Then the home sound effect should be played.
+ verify(mAudioManager).playSoundEffect(AudioManager.FX_HOME);
+ }
+
+ /**
* Sets {@link AudioManager#isHomeSoundEffectEnabled()} and starts HomeSoundEffectController.
* If the home sound effect is enabled, the registered TaskStackChangeListener is extracted.
*/
@@ -155,4 +413,20 @@
mTaskStackChangeListener = listenerCaptor.getValue();
}
}
+
+ private ActivityManager.RunningTaskInfo createRunningTaskInfo(String packageName,
+ int activityType, int taskId, boolean playHomeTransitionSound) {
+ ActivityManager.RunningTaskInfo res = new ActivityManager.RunningTaskInfo();
+ res.topActivityInfo = new ActivityInfo();
+ res.topActivityInfo.packageName = packageName;
+ res.topActivityType = activityType;
+ res.taskId = taskId;
+ if (!playHomeTransitionSound) {
+ // set the flag to 0
+ res.topActivityInfo.privateFlags &= ~ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND;
+ } else {
+ res.topActivityInfo.privateFlags |= ActivityInfo.PRIVATE_FLAG_HOME_TRANSITION_SOUND;
+ }
+ return res;
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
index c2e0d6b..8db0f33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java
@@ -453,6 +453,9 @@
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
assertThat(statusContent.getMaxLines()).isEqualTo(3);
+ // Has a single message, no count shown.
+ assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
getSizeInDp(R.dimen.required_width_for_medium) - 1);
RemoteViews smallView = new PeopleTileViewHelper(mContext,
@@ -492,6 +495,10 @@
assertEquals(View.VISIBLE, statusContent.getVisibility());
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ // Has a single message, no count shown.
+ assertEquals(View.GONE, result.findViewById(R.id.messages_count).getVisibility());
+
}
@Test
@@ -507,19 +514,62 @@
TextView name = (TextView) result.findViewById(R.id.name);
assertEquals(name.getText(), NAME);
- TextView subtext = (TextView) result.findViewById(R.id.subtext);
- assertEquals(View.GONE, subtext.getVisibility());
+ assertEquals(View.GONE, result.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, result.findViewById(R.id.predefined_icon).getVisibility());
// Has availability.
- View availability = result.findViewById(R.id.availability);
- assertEquals(View.VISIBLE, availability.getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility());
// Has person icon.
- View personIcon = result.findViewById(R.id.person_icon);
- assertEquals(View.VISIBLE, personIcon.getVisibility());
+ assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility());
// Has notification content.
TextView statusContent = (TextView) result.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
- // Has 2 messages, show count.
+ // Has two messages, show count.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_medium) - 1);
+ RemoteViews smallView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View smallResult = smallView.apply(mContext, null);
+
+ // Show icon instead of name.
+ assertEquals(View.GONE, smallResult.findViewById(R.id.name).getVisibility());
+ assertEquals(View.GONE,
+ smallResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has person icon.
+ assertEquals(View.VISIBLE,
+ smallResult.findViewById(R.id.person_icon).getVisibility());
+
+ // Has two messages, show count.
+ assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
+
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_width_for_large));
+ mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH,
+ getSizeInDp(R.dimen.required_height_for_large));
+ RemoteViews largeView = new PeopleTileViewHelper(mContext,
+ tileWithStatusAndNotification, 0, mOptions).getViews();
+ View largeResult = largeView.apply(mContext, null);
+
+ name = (TextView) largeResult.findViewById(R.id.name);
+ assertEquals(name.getText(), NAME);
+ assertEquals(View.GONE, largeResult.findViewById(R.id.subtext).getVisibility());
+ assertEquals(View.GONE, largeResult.findViewById(R.id.predefined_icon).getVisibility());
+ // Has availability.
+ assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility());
+ // Has person icon.
+ View personIcon = largeResult.findViewById(R.id.person_icon);
+ assertEquals(View.VISIBLE, personIcon.getVisibility());
+ // Has notification content.
+ statusContent = (TextView) largeResult.findViewById(R.id.text_content);
+ assertEquals(View.VISIBLE, statusContent.getVisibility());
+ assertEquals(statusContent.getText(), NOTIFICATION_CONTENT);
+ assertThat(statusContent.getMaxLines()).isEqualTo(3);
+
+ // Has two messages, show count.
assertEquals(View.VISIBLE, result.findViewById(R.id.messages_count).getVisibility());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 0bf1ac3..e65db5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar
import android.app.WallpaperManager
+import android.os.IBinder
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
@@ -64,6 +65,7 @@
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
+ @Mock private lateinit var windowToken: IBinder
@Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
@@ -80,6 +82,7 @@
@Before
fun setup() {
`when`(root.viewRootImpl).thenReturn(viewRootImpl)
+ `when`(root.windowToken).thenReturn(windowToken)
`when`(root.isAttachedToWindow).thenReturn(true)
`when`(statusBarStateController.state).then { statusBarState }
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
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/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c295778..a9eb2c1 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1141,4 +1141,9 @@
*/
public abstract boolean isPackageFrozen(
@NonNull String packageName, int callingUid, int userId);
+
+ /**
+ * Deletes the OAT artifacts of a package.
+ */
+ public abstract void deleteOatArtifactsOfPackage(String packageName);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 63639ed..8f56842 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;
@@ -30,6 +29,7 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
@@ -75,7 +75,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
import static android.net.NetworkPolicyManager.blockedReasonsToString;
import static android.net.NetworkRequest.Type.LISTEN_FOR_BEST;
import static android.net.shared.NetworkMonitorUtils.isPrivateDnsValidationRequired;
@@ -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.
*/
@@ -1145,8 +1159,8 @@
/**
* @see NetworkUtils#queryUserAccess(int, int)
*/
- public boolean queryUserAccess(int uid, int netId) {
- return NetworkUtils.queryUserAccess(uid, netId);
+ public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) {
+ return cs.queryUserAccess(uid, network);
}
/**
@@ -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;
@@ -4840,6 +4866,42 @@
nai.networkMonitor().forceReevaluation(uid);
}
+ // TODO: call into netd.
+ private boolean queryUserAccess(int uid, Network network) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return false;
+
+ // Any UID can use its default network.
+ if (nai == getDefaultNetworkForUid(uid)) return true;
+
+ // Privileged apps can use any network.
+ if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
+ return true;
+ }
+
+ // An unprivileged UID can use a VPN iff the VPN applies to it.
+ if (nai.isVPN()) {
+ return nai.networkCapabilities.appliesToUid(uid);
+ }
+
+ // An unprivileged UID can bypass the VPN that applies to it only if it can protect its
+ // sockets, i.e., if it is the owner.
+ final NetworkAgentInfo vpn = getVpnForUid(uid);
+ if (vpn != null && !vpn.networkAgentConfig.allowBypass
+ && uid != vpn.networkCapabilities.getOwnerUid()) {
+ return false;
+ }
+
+ // The UID's permission must be at least sufficient for the network. Since the restricted
+ // permission was already checked above, that just leaves background networks.
+ if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
+ return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
+ }
+
+ // Unrestricted network. Anyone gets to use it.
+ return true;
+ }
+
/**
* Returns information about the proxy a certain network is using. If given a null network, it
* it will return the proxy for the bound network for the caller app or the default proxy if
@@ -4860,7 +4922,7 @@
return null;
}
return getLinkPropertiesProxyInfo(activeNetwork);
- } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) {
+ } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
// caller may not have.
return getLinkPropertiesProxyInfo(network);
@@ -6010,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);
}
@@ -6054,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<>();
@@ -6363,6 +6454,71 @@
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));
+ }
+ }
+
+ private boolean isNetworkProviderWithIdRegistered(final int providerId) {
+ for (final NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ if (npi.providerId == providerId) return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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();
+ if (!isNetworkProviderWithIdRegistered(newOffer.providerId)) {
+ // This may actually happen if a provider updates its score or registers and then
+ // immediately unregisters. The offer would still be in the handler queue, but the
+ // provider would have been removed.
+ if (DBG) log("Received offer from an unregistered provider");
+ return;
+ }
+ 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/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 5d1ca33..09f4c22 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -143,7 +143,6 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.SomeArgs;
-import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
@@ -4268,31 +4267,37 @@
}
}
+ @Override
+ public int getExternalStorageMountMode(int uid, String packageName) {
+ enforcePermission(android.Manifest.permission.WRITE_MEDIA_STORAGE);
+ return mStorageManagerInternal.getExternalStorageMountMode(uid, packageName);
+ }
+
private int getMountModeInternal(int uid, String packageName) {
try {
// Get some easy cases out of the way first
if (Process.isIsolated(uid)) {
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
if (ArrayUtils.isEmpty(packagesForUid)) {
// It's possible the package got uninstalled already, so just ignore.
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
if (packageName == null) {
packageName = packagesForUid[0];
}
if (mPmInternal.isInstantApp(packageName, UserHandle.getUserId(uid))) {
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
if (mStorageManagerInternal.isExternalStorageService(uid)) {
// Determine if caller requires pass_through mount; note that we do this for
// all processes that share a UID with MediaProvider; but this is fine, since
// those processes anyway share the same rights as MediaProvider.
- return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+ return StorageManager.MOUNT_MODE_EXTERNAL_PASS_THROUGH;
}
if ((mDownloadsAuthorityAppId == UserHandle.getAppId(uid)
@@ -4300,7 +4305,7 @@
// DownloadManager can write in app-private directories on behalf of apps;
// give it write access to Android/
// ExternalStorageProvider can access Android/{data,obb} dirs in managed mode
- return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
}
final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
@@ -4310,7 +4315,7 @@
0, UserHandle.getUserId(uid));
if (ai != null && ai.isSignedWithPlatformKey()) {
// Platform processes hosting the MTP server should be able to write in Android/
- return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_ANDROID_WRITABLE;
}
}
@@ -4335,13 +4340,13 @@
}
}
if ((hasInstall || hasInstallOp) && hasWrite) {
- return Zygote.MOUNT_EXTERNAL_INSTALLER;
+ return StorageManager.MOUNT_MODE_EXTERNAL_INSTALLER;
}
- return Zygote.MOUNT_EXTERNAL_DEFAULT;
+ return StorageManager.MOUNT_MODE_EXTERNAL_DEFAULT;
} catch (RemoteException e) {
// Should not happen
}
- return Zygote.MOUNT_EXTERNAL_NONE;
+ return StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
private static class Callbacks extends Handler {
@@ -4711,7 +4716,8 @@
return true;
}
- return getExternalStorageMountMode(uid, packageName) != Zygote.MOUNT_EXTERNAL_NONE;
+ return getExternalStorageMountMode(uid, packageName)
+ != StorageManager.MOUNT_MODE_EXTERNAL_NONE;
}
private void killAppForOpChange(int code, int uid) {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 7276c78..411fc67 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -154,6 +154,7 @@
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int phoneId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
+ int targetSdk;
boolean matchTelephonyCallbackEvent(int event) {
return (callback != null) && (this.eventList.contains(event));
@@ -919,6 +920,8 @@
}
r.phoneId = phoneId;
r.eventList = events;
+ r.targetSdk = TelephonyPermissions.getTargetSdk(mContext, callingPackage);
+
if (DBG) {
log("listen: Register r=" + r + " r.subId=" + r.subId + " phoneId=" + phoneId);
}
@@ -1748,6 +1751,11 @@
TelephonyCallback.EVENT_DISPLAY_INFO_CHANGED)
&& idMatchWithoutDefaultPhoneCheck(r.subId, subId)) {
try {
+ if (r.targetSdk <= android.os.Build.VERSION_CODES.R) {
+ telephonyDisplayInfo =
+ getBackwardCompatibleTelephonyDisplayInfo(
+ telephonyDisplayInfo);
+ }
r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
@@ -1759,6 +1767,19 @@
}
}
+ private TelephonyDisplayInfo getBackwardCompatibleTelephonyDisplayInfo(
+ @NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+ int networkType = telephonyDisplayInfo.getNetworkType();
+ int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
+ if (networkType == TelephonyManager.NETWORK_TYPE_NR) {
+ overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE;
+ } else if (networkType == TelephonyManager.NETWORK_TYPE_LTE
+ && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
+ overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
+ }
+ return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+ }
+
public void notifyCallForwardingChanged(boolean cfi) {
notifyCallForwardingChangedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, cfi);
}
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index 77cec78..cfa110e 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -348,7 +348,8 @@
}
}
- private void enforceCallingUserAndCarrierPrivilege(ParcelUuid subscriptionGroup) {
+ private void enforceCallingUserAndCarrierPrivilege(
+ ParcelUuid subscriptionGroup, String pkgName) {
// Only apps running in the primary (system) user are allowed to configure the VCN. This is
// in line with Telephony's behavior with regards to binding to a Carrier App provided
// CarrierConfigService.
@@ -362,12 +363,15 @@
subscriptionInfos.addAll(subMgr.getSubscriptionsInGroup(subscriptionGroup));
});
- final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class);
for (SubscriptionInfo info : subscriptionInfos) {
+ final TelephonyManager telMgr = mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(info.getSubscriptionId());
+
// Check subscription is active first; much cheaper/faster check, and an app (currently)
// cannot be carrier privileged for inactive subscriptions.
if (subMgr.isValidSlotIndex(info.getSimSlotIndex())
- && telMgr.hasCarrierPrivileges(info.getSubscriptionId())) {
+ && telMgr.checkCarrierPrivilegesForPackage(pkgName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
// TODO (b/173717728): Allow configuration for inactive, but manageable
// subscriptions.
// TODO (b/173718661): Check for whole subscription groups at a time.
@@ -535,7 +539,7 @@
mContext.getSystemService(AppOpsManager.class)
.checkPackage(mDeps.getBinderCallingUid(), config.getProvisioningPackageName());
- enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
@@ -553,11 +557,14 @@
* <p>Implements the IVcnManagementService Binder interface.
*/
@Override
- public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup) {
+ public void clearVcnConfig(@NonNull ParcelUuid subscriptionGroup, @NonNull String opPkgName) {
requireNonNull(subscriptionGroup, "subscriptionGroup was null");
+ requireNonNull(opPkgName, "opPkgName was null");
Slog.v(TAG, "VCN config cleared for subGrp: " + subscriptionGroup);
- enforceCallingUserAndCarrierPrivilege(subscriptionGroup);
+ mContext.getSystemService(AppOpsManager.class)
+ .checkPackage(mDeps.getBinderCallingUid(), opPkgName);
+ enforceCallingUserAndCarrierPrivilege(subscriptionGroup, opPkgName);
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index d2fd8ff..98931a5 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1790,6 +1790,44 @@
}
if (!ignoreForeground) {
+ if (r.mStartForegroundCount == 0) {
+ /*
+ If the service was started with startService(), not
+ startForegroundService(), and if startForeground() isn't called within
+ mFgsStartForegroundTimeoutMs, then we check the state of the app
+ (who owns the service, which is the app that called startForeground())
+ again. If the app is in the foreground, or in any other cases where
+ FGS-starts are allowed, then we still allow the FGS to be started.
+ Otherwise, startForeground() would fail.
+
+ If the service was started with startForegroundService(), then the service
+ must call startForeground() within a timeout anyway, so we don't need this
+ check.
+ */
+ if (!r.fgRequired) {
+ final long delayMs = SystemClock.elapsedRealtime() - r.createRealTime;
+ if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, false);
+ final String temp = "startForegroundDelayMs:" + delayMs;
+ if (r.mInfoAllowStartForeground != null) {
+ r.mInfoAllowStartForeground += "; " + temp;
+ } else {
+ r.mInfoAllowStartForeground = temp;
+ }
+ r.mLoggedInfoAllowStartForeground = false;
+ }
+ }
+ } else if (r.mStartForegroundCount >= 1) {
+ // The second or later time startForeground() is called after service is
+ // started. Check for app state again.
+ final long delayMs = SystemClock.elapsedRealtime() -
+ r.mLastSetFgsRestrictionTime;
+ if (delayMs > mAm.mConstants.mFgsStartForegroundTimeoutMs) {
+ setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+ r.appInfo.uid, r.intent.getIntent(), r, false);
+ }
+ }
logFgsBackgroundStart(r);
if (r.mAllowStartForeground == REASON_DENIED && isBgFgsRestrictionEnabled(r)) {
final String msg = "Service.startForeground() not allowed due to "
@@ -1843,6 +1881,7 @@
active.mNumActive++;
}
r.isForeground = true;
+ r.mStartForegroundCount++;
if (!stopProcStatsOp) {
ServiceState stracker = r.getTracker();
if (stracker != null) {
@@ -1901,6 +1940,7 @@
decActiveForegroundAppLocked(smap, r);
}
r.isForeground = false;
+ resetFgsRestrictionLocked(r);
ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setForeground(false, mAm.mProcessStats.getMemFactorLocked(),
@@ -3892,6 +3932,7 @@
r.foregroundId = 0;
r.foregroundNoti = null;
r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowStartForeground = REASON_DENIED;
// Clear start entries.
r.clearDeliveredStartsLocked();
@@ -5430,6 +5471,7 @@
private void setFgsRestrictionLocked(String callingPackage,
int callingPid, int callingUid, Intent intent, ServiceRecord r,
boolean allowBackgroundActivityStarts) {
+ r.mLastSetFgsRestrictionTime = SystemClock.elapsedRealtime();
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
r.mAllowWhileInUsePermissionInFgs = true;
@@ -5450,6 +5492,15 @@
}
}
+ void resetFgsRestrictionLocked(ServiceRecord r) {
+ r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowStartForeground = REASON_DENIED;
+ r.mInfoAllowStartForeground = null;
+ r.mInfoTempFgsAllowListReason = null;
+ r.mLoggedInfoAllowStartForeground = false;
+ r.mLastSetFgsRestrictionTime = 0;
+ }
+
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
return true;
@@ -5601,6 +5652,7 @@
+ ",callingUid:" + tempAllowListReason.mCallingUid))
+ ">"
+ "; targetSdkVersion:" + r.appInfo.targetSdkVersion
+ + "; startForegroundCount:" + r.mStartForegroundCount
+ "]";
if (!debugInfo.equals(r.mInfoAllowStartForeground)) {
r.mLoggedInfoAllowStartForeground = false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5859cea..f7abf6a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -96,6 +96,7 @@
static final String KEY_PROCESS_CRASH_COUNT_LIMIT = "process_crash_count_limit";
static final String KEY_BOOT_TIME_TEMP_ALLOWLIST_DURATION = "boot_time_temp_allowlist_duration";
static final String KEY_FG_TO_BG_FGS_GRACE_DURATION = "fg_to_bg_fgs_grace_duration";
+ static final String KEY_FGS_START_FOREGROUND_TIMEOUT = "fgs_start_foreground_timeout";
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
@@ -135,7 +136,7 @@
private static final int DEFAULT_PROCESS_CRASH_COUNT_LIMIT = 12;
private static final int DEFAULT_BOOT_TIME_TEMP_ALLOWLIST_DURATION = 10 * 1000;
private static final long DEFAULT_FG_TO_BG_FGS_GRACE_DURATION = 5 * 1000;
-
+ private static final int DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS = 10 * 1000;
// Flag stored in the DeviceConfig API.
/**
@@ -396,6 +397,12 @@
*/
volatile long mFgToBgFgsGraceDuration = DEFAULT_FG_TO_BG_FGS_GRACE_DURATION;
+ /**
+ * When service started from background, before the timeout it can be promoted to FGS by calling
+ * Service.startForeground().
+ */
+ volatile long mFgsStartForegroundTimeoutMs = DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS;
+
private final ActivityManagerService mService;
private ContentResolver mResolver;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -586,6 +593,9 @@
case KEY_FG_TO_BG_FGS_GRACE_DURATION:
updateFgToBgFgsGraceDuration();
break;
+ case KEY_FGS_START_FOREGROUND_TIMEOUT:
+ updateFgsStartForegroundTimeout();
+ break;
default:
break;
}
@@ -869,6 +879,13 @@
DEFAULT_FG_TO_BG_FGS_GRACE_DURATION);
}
+ private void updateFgsStartForegroundTimeout() {
+ mFgsStartForegroundTimeoutMs = DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_FGS_START_FOREGROUND_TIMEOUT,
+ DEFAULT_FGS_START_FOREGROUND_TIMEOUT_MS);
+ }
+
private void updateImperceptibleKillExemptions() {
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.clear();
IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
@@ -1071,6 +1088,8 @@
pw.println(mBootTimeTempAllowlistDuration);
pw.print(" "); pw.print(KEY_FG_TO_BG_FGS_GRACE_DURATION); pw.print("=");
pw.println(mFgToBgFgsGraceDuration);
+ pw.print(" "); pw.print(KEY_FGS_START_FOREGROUND_TIMEOUT); pw.print("=");
+ pw.println(mFgsStartForegroundTimeoutMs);
pw.println();
if (mOverrideMaxCachedProcesses >= 0) {
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index e74c936..5b2276c 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -487,8 +487,10 @@
Slog.wtf(TAG, "Error updating external stats: ", e);
}
- synchronized (BatteryExternalStatsWorker.this) {
- mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
+ if ((updateFlags & UPDATE_ALL) == UPDATE_ALL) {
+ synchronized (BatteryExternalStatsWorker.this) {
+ mLastCollectionTimeStamp = SystemClock.elapsedRealtime();
+ }
}
}
};
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index c3f97ad..3b76021 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -674,6 +674,13 @@
public List<BatteryUsageStats> getBatteryUsageStats(List<BatteryUsageStatsQuery> queries) {
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
+ awaitCompletion();
+
+ if (mBatteryUsageStatsProvider.shouldUpdateStats(queries,
+ mWorker.getLastCollectionTimeStamp())) {
+ syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
+ }
+
return mBatteryUsageStatsProvider.getBatteryUsageStats(queries);
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index c5f082a..1839e2a 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -382,10 +382,13 @@
@GuardedBy("mProcLock")
void compactAppSome(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -396,10 +399,13 @@
&& app.mState.getCurAdj() >= mCompactThrottleMinOomAdj
&& app.mState.getCurAdj() <= mCompactThrottleMaxOomAdj) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
- COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
+ COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState()));
+ }
} else {
if (DEBUG_COMPACTION) {
Slog.d(TAG_AM, "Skipping full compaction for " + app.processName
@@ -412,10 +418,13 @@
@GuardedBy("mProcLock")
void compactAppPersistent(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -427,10 +436,13 @@
@GuardedBy("mProcLock")
void compactAppBfgs(ProcessRecord app) {
app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS);
- mPendingCompactionProcesses.add(app);
- mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ if (!app.mOptRecord.hasPendingCompact()) {
+ app.mOptRecord.setHasPendingCompact(true);
+ mPendingCompactionProcesses.add(app);
+ mCompactionHandler.sendMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState()));
+ }
}
@GuardedBy("mProcLock")
@@ -954,6 +966,7 @@
pendingAction = opt.getReqCompactAction();
pid = proc.getPid();
name = proc.processName;
+ opt.setHasPendingCompact(false);
// don't compact if the process has returned to perceptible
// and this is only a cached/home/prev compaction
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 4643610..f4ce723 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -47,6 +47,12 @@
private int mLastCompactAction;
/**
+ * This process has been scheduled for a memory compaction.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mPendingCompact;
+
+ /**
* True when the process is frozen.
*/
@GuardedBy("mProcLock")
@@ -101,6 +107,16 @@
}
@GuardedBy("mProcLock")
+ boolean hasPendingCompact() {
+ return mPendingCompact;
+ }
+
+ @GuardedBy("mProcLock")
+ void setHasPendingCompact(boolean pendingCompact) {
+ mPendingCompact = pendingCompact;
+ }
+
+ @GuardedBy("mProcLock")
boolean isFrozen() {
return mFrozen;
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 9cd9902..54c3512 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -165,9 +165,16 @@
// allow the service becomes foreground service? Service started from background may not be
// allowed to become a foreground service.
@PowerWhitelistManager.ReasonCode int mAllowStartForeground = REASON_DENIED;
+ // Debug info why mAllowStartForeground is allowed or denied.
String mInfoAllowStartForeground;
+ // Debug info if mAllowStartForeground is allowed because of a temp-allowlist.
FgsStartTempAllowList.TempFgsAllowListEntry mInfoTempFgsAllowListReason;
+ // Is the same mInfoAllowStartForeground string has been logged before? Used for dedup.
boolean mLoggedInfoAllowStartForeground;
+ // The number of times Service.startForeground() is called;
+ int mStartForegroundCount;
+ // Last time mAllowWhileInUsePermissionInFgs or mAllowStartForeground is set.
+ long mLastSetFgsRestrictionTime;
String stringName; // caching of toString
@@ -439,6 +446,8 @@
pw.println(mRecentCallingUid);
pw.print(prefix); pw.print("allowStartForeground=");
pw.println(mAllowStartForeground);
+ pw.print(prefix); pw.print("startForegroundCount=");
+ pw.println(mStartForegroundCount);
pw.print(prefix); pw.print("infoAllowStartForeground=");
pw.println(mInfoAllowStartForeground);
if (delayed) {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 143a1cf..31183cac 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1641,6 +1641,13 @@
* PIN or pattern.
*/
private boolean maybeUnlockUser(final @UserIdInt int userId) {
+ if (mInjector.isFileEncryptedNativeOnly() && mLockPatternUtils.isSecure(userId)) {
+ // A token is needed, so don't bother trying to unlock without one.
+ // This keeps misleading error messages from being logged.
+ Slog.d(TAG, "Not unlocking user " + userId
+ + "'s CE storage yet because a credential token is needed");
+ return false;
+ }
// Try unlocking storage using empty token
return unlockUserCleared(userId, null, null, null);
}
@@ -3101,5 +3108,11 @@
protected IStorageManager getStorageManager() {
return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
}
+
+ // This is needed because isFileEncryptedNativeOnly is a static method,
+ // but it needs to be mocked out in tests.
+ protected boolean isFileEncryptedNativeOnly() {
+ return StorageManager.isFileEncryptedNativeOnly();
+ }
}
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index ebe9d96..5a99e0e 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -38,6 +38,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Environment;
import android.os.RemoteException;
@@ -92,8 +93,10 @@
private final Object mLock = new Object();
private final Context mContext;
private final IPackageManager mIPackageManager;
+ private final PackageManagerInternal mPackageManagerInternal;
private final IActivityManager mIActivityManager;
private final UserManager mUserManager;
+
@GuardedBy("mLock")
private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>();
private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores =
@@ -125,6 +128,7 @@
super(injector.getContext());
mContext = injector.getContext();
mIPackageManager = injector.getPackageManager();
+ mPackageManagerInternal = injector.getPackageManagerInternal();
mIActivityManager = injector.getActivityManager();
mUserManager = injector.getUserManager();
mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore();
@@ -214,8 +218,9 @@
synchronized (mLock) {
GlobalLevelState state = mGlobalHibernationStates.get(packageName);
if (state == null) {
- throw new IllegalArgumentException(
- String.format("Package %s is not installed", packageName));
+ // This API can be legitimately called before installation finishes as part of
+ // dex optimization, so we just return false here.
+ return false;
}
return state.hibernated;
}
@@ -366,7 +371,7 @@
@GuardedBy("mLock")
private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally");
- // TODO(175830194): Delete vdex/odex when DexManager API is built out
+ mPackageManagerInternal.deleteOatArtifactsOfPackage(packageName);
state.hibernated = true;
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
}
@@ -728,6 +733,8 @@
IPackageManager getPackageManager();
+ PackageManagerInternal getPackageManagerInternal();
+
IActivityManager getActivityManager();
UserManager getUserManager();
@@ -762,6 +769,11 @@
}
@Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ @Override
public IActivityManager getActivityManager() {
return ActivityManager.getService();
}
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/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 804550b..2ce60d0 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1836,6 +1836,127 @@
}
}
+ /** @see AudioManager#isSurroundFormatEnabled(int) */
+ @Override
+ public boolean isSurroundFormatEnabled(int audioFormat) {
+ if (!isSurroundFormat(audioFormat)) {
+ Log.w(TAG, "audioFormat to enable is not a surround format.");
+ return false;
+ }
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ HashSet<Integer> enabledFormats = getEnabledFormats();
+ return enabledFormats.contains(audioFormat);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** @see AudioManager#setSurroundFormatEnabled(int, boolean) */
+ @Override
+ public boolean setSurroundFormatEnabled(int audioFormat, boolean enabled) {
+ if (!isSurroundFormat(audioFormat)) {
+ Log.w(TAG, "audioFormat to enable is not a surround format.");
+ return false;
+ }
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ HashSet<Integer> enabledFormats = getEnabledFormats();
+ if (enabled) {
+ enabledFormats.add(audioFormat);
+ } else {
+ enabledFormats.remove(audioFormat);
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ Settings.Global.putString(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS,
+ TextUtils.join(",", enabledFormats));
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+
+ /** @see AudioManager#setEncodedSurroundMode(int) */
+ @Override
+ public boolean setEncodedSurroundMode(int mode) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ mode);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return true;
+ }
+
+ /** @see AudioManager#getEncodedSurroundMode() */
+ @Override
+ public int getEncodedSurroundMode() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Missing WRITE_SETTINGS permission");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSettingsLock) {
+ return Settings.Global.getInt(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT,
+ AudioManager.ENCODED_SURROUND_OUTPUT_AUTO);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /** @return the formats that are enabled in global settings */
+ private HashSet<Integer> getEnabledFormats() {
+ HashSet<Integer> formats = new HashSet<>();
+ String enabledFormats = Settings.Global.getString(mContentResolver,
+ Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
+ if (enabledFormats != null) {
+ try {
+ Arrays.stream(TextUtils.split(enabledFormats, ","))
+ .mapToInt(Integer::parseInt)
+ .forEach(formats::add);
+ } catch (NumberFormatException e) {
+ Log.w(TAG, "ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS misformatted.", e);
+ }
+ }
+ return formats;
+ }
+
+ private boolean isSurroundFormat(int audioFormat) {
+ for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
+ if (sf == audioFormat) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void sendEnabledSurroundFormats(ContentResolver cr, boolean forceUpdate) {
if (mEncodedSurroundMode != Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL) {
// Manually enable surround formats only when the setting is in manual mode.
@@ -1860,14 +1981,7 @@
for (String format : surroundFormats) {
try {
int audioFormat = Integer.valueOf(format);
- boolean isSurroundFormat = false;
- for (int sf : AudioFormat.SURROUND_SOUND_ENCODING) {
- if (sf == audioFormat) {
- isSurroundFormat = true;
- break;
- }
- }
- if (isSurroundFormat && !formats.contains(audioFormat)) {
+ if (isSurroundFormat(audioFormat) && !formats.contains(audioFormat)) {
formats.add(audioFormat);
}
} catch (Exception e) {
@@ -7343,7 +7457,6 @@
Settings.Global.ENCODED_SURROUND_OUTPUT_AUTO);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.ENCODED_SURROUND_OUTPUT), false, this);
-
mEnabledSurroundFormats = Settings.Global.getString(
mContentResolver, Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
mContentResolver.registerContentObserver(Settings.Global.getUriFor(
diff --git a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
index 8b9be83..3d69326 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StartUserClient.java
@@ -25,18 +25,27 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
-public abstract class StartUserClient<T> extends HalClientMonitor<T> {
+/**
+ * Abstract class for starting a new user.
+ * @param <T> Interface to request a new user.
+ * @param <U> Newly created user object.
+ */
+public abstract class StartUserClient<T, U> extends HalClientMonitor<T> {
- public interface UserStartedCallback {
- void onUserStarted(int newUserId);
+ /**
+ * Invoked when the new user is started.
+ * @param <U> New user object.
+ */
+ public interface UserStartedCallback<U> {
+ void onUserStarted(int newUserId, U newUser);
}
@NonNull @VisibleForTesting
- protected final UserStartedCallback mUserStartedCallback;
+ protected final UserStartedCallback<U> mUserStartedCallback;
public StartUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
- @NonNull UserStartedCallback callback) {
+ @NonNull UserStartedCallback<U> callback) {
super(context, lazyDaemon, token, null /* listener */, userId, context.getOpPackageName(),
0 /* cookie */, sensorId, BiometricsProtoEnums.MODALITY_UNKNOWN,
BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
diff --git a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
index 62cd673..1f6e1e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/StopUserClient.java
@@ -25,6 +25,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+/**
+ * Abstract class for stopping a user.
+ * @param <T> Interface for stopping the user.
+ */
public abstract class StopUserClient<T> extends HalClientMonitor<T> {
public interface UserStoppedCallback {
@@ -32,7 +36,12 @@
}
@NonNull @VisibleForTesting
- protected final UserStoppedCallback mUserStoppedCallback;
+ private final UserStoppedCallback mUserStoppedCallback;
+
+ public void onUserStopped() {
+ mUserStoppedCallback.onUserStopped();
+ getCallback().onClientFinished(this, true /* success */);
+ }
public StopUserClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@Nullable IBinder token, int userId, int sensorId,
diff --git a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
index c0ea2b3..f015a80 100644
--- a/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/UserAwareBiometricScheduler.java
@@ -45,13 +45,15 @@
public interface UserSwitchCallback {
@NonNull StopUserClient<?> getStopUserClient(int userId);
- @NonNull StartUserClient<?> getStartUserClient(int newUserId);
+ @NonNull StartUserClient<?, ?> getStartUserClient(int newUserId);
}
@NonNull private final CurrentUserRetriever mCurrentUserRetriever;
@NonNull private final UserSwitchCallback mUserSwitchCallback;
@NonNull @VisibleForTesting final ClientFinishedCallback mClientFinishedCallback;
+ @Nullable private StopUserClient<?> mStopUserClient;
+
@VisibleForTesting
class ClientFinishedCallback implements BaseClientMonitor.Callback {
@Override
@@ -108,16 +110,31 @@
if (nextUserId == currentUserId) {
super.startNextOperationIfIdle();
} else if (currentUserId == UserHandle.USER_NULL) {
- Slog.d(getTag(), "User switch required, current user null, next: " + nextUserId);
final BaseClientMonitor startClient =
mUserSwitchCallback.getStartUserClient(nextUserId);
+ Slog.d(getTag(), "[Starting User] " + startClient);
startClient.start(mClientFinishedCallback);
} else {
- final BaseClientMonitor stopClient = mUserSwitchCallback
- .getStopUserClient(currentUserId);
- Slog.d(getTag(), "User switch required, current: " + currentUserId
- + ", next: " + nextUserId + ". " + stopClient);
- stopClient.start(mClientFinishedCallback);
+ if (mStopUserClient != null) {
+ Slog.d(getTag(), "[Waiting for StopUser] " + mStopUserClient);
+ } else {
+ mStopUserClient = mUserSwitchCallback
+ .getStopUserClient(currentUserId);
+ Slog.d(getTag(), "[Stopping User] current: " + currentUserId
+ + ", next: " + nextUserId + ". " + mStopUserClient);
+ mStopUserClient.start(mClientFinishedCallback);
+ }
}
}
+
+ public void onUserStopped() {
+ if (mStopUserClient == null) {
+ Slog.e(getTag(), "Unexpected onUserStopped");
+ return;
+ }
+
+ Slog.d(getTag(), "[OnUserStopped]: " + mStopUserClient);
+ mStopUserClient.onUserStopped();
+ mStopUserClient = null;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 9f5dc69..d10fd4f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -502,9 +502,6 @@
return false;
}
- final boolean enrolled = provider.getEnrolledFaces(sensorId, userId).size() > 0;
- Slog.d(TAG, "hasEnrolledFaces, sensor: " + sensorId + ", enrolled: " + enrolled);
-
return provider.getEnrolledFaces(sensorId, userId).size() > 0;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index fdc3bb1..a9be8e1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -78,7 +78,6 @@
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
- @NonNull private final HalClientMonitor.LazyDaemon<IFace> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final UsageStats mUsageStats;
@@ -126,7 +125,6 @@
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
- mLazyDaemon = this::getHalInstance;
mHandler = new Handler(Looper.getMainLooper());
mUsageStats = new UsageStats(context);
mLockoutResetDispatcher = lockoutResetDispatcher;
@@ -163,7 +161,8 @@
}
@Nullable
- private synchronized IFace getHalInstance() {
+ @VisibleForTesting
+ synchronized IFace getHalInstance() {
if (mTestHalEnabled) {
return new TestHal();
}
@@ -214,22 +213,6 @@
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
- private void createNewSessionWithoutHandler(@NonNull IFace daemon, int sensorId,
- int userId) throws RemoteException {
- // Note that per IFace createSession contract, this method will block until all
- // existing operations are canceled/finished. However, also note that this is fine, since
- // this method "withoutHandler" means it should only ever be invoked from the worker thread,
- // so callers will never be blocked.
- mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
- if (FaceUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
- Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
- + ", user: " + userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
-
-
private void scheduleLoadAuthenticatorIds(int sensorId) {
for (UserInfo user : UserManager.get(mContext).getAliveUsers()) {
scheduleLoadAuthenticatorIdsForUser(sensorId, user.id);
@@ -238,32 +221,16 @@
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
- return;
- }
+ final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceGetAuthenticatorIdClient client = new FaceGetAuthenticatorIdClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
- + ", sensorId: " + sensorId
- + ", userId: " + userId, e);
- }
+ scheduleForSensor(sensorId, client);
});
}
- private void scheduleInvalidationRequest(int sensorId, int userId) {
+ void scheduleInvalidationRequest(int sensorId, int userId) {
mHandler.post(() -> {
final InvalidationRequesterClient<Face> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
@@ -303,25 +270,10 @@
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
- + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
+ final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ scheduleForSensor(sensorId, client);
});
}
@@ -344,25 +296,10 @@
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFaceServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
- }
+ final FaceGenerateChallengeClient client = new FaceGenerateChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName, sensorId);
+ scheduleForSensor(sensorId, client);
});
}
@@ -370,25 +307,10 @@
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
- challenge);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
- }
+ final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token, opPackageName, sensorId,
+ challenge);
+ scheduleForSensor(sensorId, client);
});
}
@@ -398,41 +320,24 @@
@NonNull String opPackageName, @NonNull int[] disabledFeatures,
@Nullable NativeHandle previewSurface, boolean debugConsent) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final int maxTemplatesPerUser = mSensors.get(
- sensorId).getSensorProperties().maxEnrollmentsPerUser;
- final FaceEnrollClient client = new FaceEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
- ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
- debugConsent);
- scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
+ final int maxTemplatesPerUser = mSensors.get(
+ sensorId).getSensorProperties().maxEnrollmentsPerUser;
+ final FaceEnrollClient client = new FaceEnrollClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FaceUtils.getInstance(sensorId), disabledFeatures,
+ ENROLL_TIMEOUT_SEC, previewSurface, sensorId, maxTemplatesPerUser,
+ debugConsent);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
}
- });
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling enroll", e);
- }
+ }
+ });
});
}
@@ -447,31 +352,14 @@
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
- mUsageStats, mSensors.get(sensorId).getLockoutCache(),
- allowBackgroundAuthentication);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FaceAuthenticationClient client = new FaceAuthenticationClient(
+ mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ operationId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ mUsageStats, mSensors.get(sensorId).getLockoutCache(),
+ allowBackgroundAuthentication);
+ scheduleForSensor(sensorId, client);
});
}
@@ -503,56 +391,24 @@
private void scheduleRemoveSpecifiedIds(int sensorId, @NonNull IBinder token, int[] faceIds,
int userId, @NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceRemovalClient client = new FaceRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), faceIds, userId,
- opPackageName, FaceUtils.getInstance(sensorId), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling remove", e);
- }
+ final FaceRemovalClient client = new FaceRemovalClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), faceIds, userId,
+ opPackageName, FaceUtils.getInstance(sensorId), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@Override
public void scheduleResetLockout(int sensorId, int userId, @NonNull byte[] hardwareAuthToken) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
- return;
- }
+ final FaceResetLockoutClient client = new FaceResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FaceResetLockoutClient client = new FaceResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
- }
+ scheduleForSensor(sensorId, client);
});
}
@@ -580,29 +436,14 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
mHandler.post(() -> {
- final IFace daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
- final FaceInternalCleanupClient client =
- new FaceInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FaceUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
-
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
- }
+ final List<Face> enrolledList = getEnrolledFaces(sensorId, userId);
+ final FaceInternalCleanupClient client =
+ new FaceInternalCleanupClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, enrolledList,
+ FaceUtils.getInstance(sensorId),
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
new file mode 100644
index 0000000..c364dbb
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStartUserClient.java
@@ -0,0 +1,67 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.IFace;
+import android.hardware.biometrics.face.ISession;
+import android.hardware.biometrics.face.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FaceStartUserClient extends StartUserClient<IFace, ISession> {
+ private static final String TAG = "FaceStartUserClient";
+
+ @NonNull private final ISessionCallback mSessionCallback;
+
+ public FaceStartUserClient(@NonNull Context context, @NonNull LazyDaemon<IFace> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull ISessionCallback sessionCallback,
+ @NonNull UserStartedCallback<ISession> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ mSessionCallback = sessionCallback;
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ getTargetUserId(), mSessionCallback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ getCallback().onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
new file mode 100644
index 0000000..8d3853b
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.sensors.face.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.face.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FaceStopUserClient extends StopUserClient<ISession> {
+ private static final String TAG = "FaceStopUserClient";
+
+ public FaceStopUserClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().close(mSequentialId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 3eb4759..768f464 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -33,8 +33,11 @@
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -54,6 +57,9 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.face.FaceUtils;
import java.util.ArrayList;
@@ -70,9 +76,10 @@
@NonNull private final String mTag;
@NonNull private final FaceProvider mProvider;
@NonNull private final Context mContext;
+ @NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FaceSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@NonNull private final HalClientMonitor.LazyDaemon<ISession> mLazySession;
@@ -112,14 +119,14 @@
@NonNull
private final String mTag;
@NonNull
- private final BiometricScheduler mScheduler;
+ private final UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull
private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -426,9 +433,7 @@
@Override
public void onSessionClosed() {
- mHandler.post(() -> {
- // TODO: implement this.
- });
+ mHandler.post(mScheduler::onUserStopped);
}
}
@@ -437,9 +442,53 @@
mTag = tag;
mProvider = provider;
mContext = context;
+ mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new BiometricScheduler(tag, null /* gestureAvailabilityDispatcher */);
+ mScheduler = new UserAwareBiometricScheduler(tag, null /* gestureAvailabilityDispatcher */,
+ () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new FaceStopUserClient(mContext, mLazySession, mToken, userId,
+ mSensorProperties.sensorId, () -> mCurrentSession = null);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ };
+
+ final int sensorId = mSensorProperties.sensorId;
+
+ final HalSessionCallback resultController = new HalSessionCallback(mContext,
+ mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+ final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+ (userIdStarted, newSession) -> {
+ mCurrentSession = new Session(mTag, newSession, userIdStarted,
+ resultController);
+ if (FaceUtils.getLegacyInstance(sensorId)
+ .isInvalidationInProgress(mContext, userIdStarted)) {
+ Slog.w(mTag,
+ "Scheduling unfinished invalidation request for "
+ + "sensor: "
+ + sensorId
+ + ", user: " + userIdStarted);
+ provider.scheduleInvalidationRequest(sensorId,
+ userIdStarted);
+ }
+ };
+
+ return new FaceStartUserClient(mContext, provider::getHalInstance,
+ mToken, newUserId, mSensorProperties.sensorId,
+ resultController, userStartedCallback);
+ }
+ });
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -453,11 +502,6 @@
return mSensorProperties;
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean hasSessionForUser(int userId) {
- return mCurrentSession != null && mCurrentSession.mUserId == userId;
- }
-
@Nullable Session getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
return mCurrentSession;
@@ -471,20 +515,6 @@
mProvider, this);
}
- void createNewSession(@NonNull IFace daemon, int sensorId, int userId)
- throws RemoteException {
-
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
- final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
- mTag, mScheduler, sensorId, userId, callback);
-
- final ISession newSession = daemon.createSession(sensorId, userId, resultController);
- mCurrentSession = new Session(mTag, newSession, userId, resultController);
- }
-
@NonNull BiometricScheduler getScheduler() {
return mScheduler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
index 4ca85d0..36327bb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java
@@ -135,7 +135,8 @@
@Override
public void close(int cookie) throws RemoteException {
- cb.onStateChanged(cookie, SessionState.CLOSED);
+ Slog.w(TAG, "close, cookie: " + cookie);
+ cb.onSessionClosed();
}
};
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index bcca69b..972071c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -82,7 +82,6 @@
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
- @NonNull private final HalClientMonitor.LazyDaemon<IFingerprint> mLazyDaemon;
@NonNull private final Handler mHandler;
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@@ -131,7 +130,6 @@
mContext = context;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
- mLazyDaemon = this::getHalInstance;
mHandler = new Handler(Looper.getMainLooper());
mLockoutResetDispatcher = lockoutResetDispatcher;
mActivityTaskManager = ActivityTaskManager.getInstance();
@@ -169,7 +167,8 @@
}
@Nullable
- private synchronized IFingerprint getHalInstance() {
+ @VisibleForTesting
+ synchronized IFingerprint getHalInstance() {
if (mTestHalEnabled) {
// Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
// the test HAL for all sensors under that HAL. This can be updated in the future if
@@ -224,21 +223,6 @@
mSensors.get(sensorId).getScheduler().scheduleClientMonitor(client, callback);
}
- private void createNewSessionWithoutHandler(@NonNull IFingerprint daemon, int sensorId,
- int userId) throws RemoteException {
- // Note that per IFingerprint createSession contract, this method will block until all
- // existing operations are canceled/finished. However, also note that this is fine, since
- // this method "withoutHandler" means it should only ever be invoked from the worker thread,
- // so callers will never be blocked.
- mSensors.get(sensorId).createNewSession(daemon, sensorId, userId);
-
- if (FingerprintUtils.getInstance(sensorId).isInvalidationInProgress(mContext, userId)) {
- Slog.w(getTag(), "Scheduling unfinished invalidation request for sensor: " + sensorId
- + ", user: " + userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
- }
-
@Override
public boolean containsSensor(int sensorId) {
return mSensors.contains(sensorId);
@@ -275,32 +259,16 @@
private void scheduleLoadAuthenticatorIdsForUser(int sensorId, int userId) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during loadAuthenticatorIds, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintGetAuthenticatorIdClient client =
- new FingerprintGetAuthenticatorIdClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling loadAuthenticatorId"
- + ", sensorId: " + sensorId
- + ", userId: " + userId, e);
- }
+ final FingerprintGetAuthenticatorIdClient client =
+ new FingerprintGetAuthenticatorIdClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
- private void scheduleInvalidationRequest(int sensorId, int userId) {
+ void scheduleInvalidationRequest(int sensorId, int userId) {
mHandler.post(() -> {
final InvalidationRequesterClient<Fingerprint> client =
new InvalidationRequesterClient<>(mContext, userId, sensorId,
@@ -312,25 +280,11 @@
@Override
public void scheduleResetLockout(int sensorId, int userId, @Nullable byte[] hardwareAuthToken) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during resetLockout, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
- mContext, mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, hardwareAuthToken,
- mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling resetLockout", e);
- }
+ final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(
+ mContext, mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, hardwareAuthToken,
+ mSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher);
+ scheduleForSensor(sensorId, client);
});
}
@@ -338,26 +292,12 @@
public void scheduleGenerateChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, String opPackageName) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during generateChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintGenerateChallengeClient client =
- new FingerprintGenerateChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), opPackageName,
- sensorId);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling generateChallenge", e);
- }
+ final FingerprintGenerateChallengeClient client =
+ new FingerprintGenerateChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), opPackageName,
+ sensorId);
+ scheduleForSensor(sensorId, client);
});
}
@@ -365,25 +305,11 @@
public void scheduleRevokeChallenge(int sensorId, int userId, @NonNull IBinder token,
@NonNull String opPackageName, long challenge) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during revokeChallenge, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintRevokeChallengeClient client =
- new FingerprintRevokeChallengeClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- opPackageName, sensorId, challenge);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling revokeChallenge", e);
- }
+ final FingerprintRevokeChallengeClient client =
+ new FingerprintRevokeChallengeClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ opPackageName, sensorId, challenge);
+ scheduleForSensor(sensorId, client);
});
}
@@ -392,40 +318,23 @@
int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during enroll, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
- .maxEnrollmentsPerUser;
- final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
- scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
- @Override
- public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
- boolean success) {
- if (success) {
- scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
- scheduleInvalidationRequest(sensorId, userId);
- }
+ final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
+ .maxEnrollmentsPerUser;
+ final FingerprintEnrollClient client = new FingerprintEnrollClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
+ opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ mUdfpsOverlayController, maxTemplatesPerUser, enrollReason);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.Callback() {
+ @Override
+ public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
+ boolean success) {
+ if (success) {
+ scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
+ scheduleInvalidationRequest(sensorId, userId);
}
- });
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling enroll", e);
- }
+ }
+ });
});
}
@@ -439,29 +348,12 @@
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
int statsClient) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during finger detect, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
- opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
- statsClient);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling finger detect", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
+ statsClient);
+ scheduleForSensor(sensorId, client);
});
}
@@ -471,31 +363,14 @@
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during authenticate, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
- final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
- false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
- mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
- mUdfpsOverlayController, allowBackgroundAuthentication);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling authenticate", e);
- }
+ final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
+ final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
+ mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ operationId, restricted, opPackageName, cookie,
+ false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
+ mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
+ mUdfpsOverlayController, allowBackgroundAuthentication);
+ scheduleForSensor(sensorId, client);
});
}
@@ -535,29 +410,12 @@
int[] fingerprintIds, int userId, @NonNull IFingerprintServiceReceiver receiver,
@NonNull String opPackageName) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during remove, sensorId: " + sensorId);
- // If this happens, we need to send HW_UNAVAILABLE after the scheduler gets to
- // this operation. We should not send the callback yet, since the scheduler may
- // be processing something else.
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
- mSensors.get(sensorId).getLazySession(), token,
- new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
- opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
- mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling remove", e);
- }
+ final FingerprintRemovalClient client = new FingerprintRemovalClient(mContext,
+ mSensors.get(sensorId).getLazySession(), token,
+ new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
+ opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@@ -565,28 +423,14 @@
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during internal cleanup, sensorId: " + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
- final FingerprintInternalCleanupClient client =
- new FingerprintInternalCleanupClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId,
- mContext.getOpPackageName(), sensorId, enrolledList,
- FingerprintUtils.getInstance(sensorId),
- mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception when scheduling internal cleanup", e);
- }
+ final List<Fingerprint> enrolledList = getEnrolledFingerprints(sensorId, userId);
+ final FingerprintInternalCleanupClient client =
+ new FingerprintInternalCleanupClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId,
+ mContext.getOpPackageName(), sensorId, enrolledList,
+ FingerprintUtils.getInstance(sensorId),
+ mSensors.get(sensorId).getAuthenticatorIds());
+ scheduleForSensor(sensorId, client);
});
}
@@ -611,26 +455,11 @@
public void scheduleInvalidateAuthenticatorId(int sensorId, int userId,
@NonNull IInvalidationCallback callback) {
mHandler.post(() -> {
- final IFingerprint daemon = getHalInstance();
- if (daemon == null) {
- Slog.e(getTag(), "Null daemon during scheduleInvalidateAuthenticatorId: "
- + sensorId);
- return;
- }
-
- try {
- if (!mSensors.get(sensorId).hasSessionForUser(userId)) {
- createNewSessionWithoutHandler(daemon, sensorId, userId);
- }
-
- final FingerprintInvalidationClient client =
- new FingerprintInvalidationClient(mContext,
- mSensors.get(sensorId).getLazySession(), userId, sensorId,
- mSensors.get(sensorId).getAuthenticatorIds(), callback);
- scheduleForSensor(sensorId, client);
- } catch (RemoteException e) {
- Slog.e(getTag(), "Remote exception", e);
- }
+ final FingerprintInvalidationClient client =
+ new FingerprintInvalidationClient(mContext,
+ mSensors.get(sensorId).getLazySession(), userId, sensorId,
+ mSensors.get(sensorId).getAuthenticatorIds(), callback);
+ scheduleForSensor(sensorId, client);
});
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
new file mode 100644
index 0000000..2d40c91
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStartUserClient.java
@@ -0,0 +1,68 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.IFingerprint;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.hardware.biometrics.fingerprint.ISessionCallback;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StartUserClient;
+
+public class FingerprintStartUserClient extends StartUserClient<IFingerprint, ISession> {
+ private static final String TAG = "FingerprintStartUserClient";
+
+ @NonNull private final ISessionCallback mSessionCallback;
+
+ public FingerprintStartUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<IFingerprint> lazyDaemon,
+ @Nullable IBinder token, int userId, int sensorId,
+ @NonNull ISessionCallback sessionCallback,
+ @NonNull UserStartedCallback<ISession> callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ mSessionCallback = sessionCallback;
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ final ISession newSession = getFreshDaemon().createSession(getSensorId(),
+ getTargetUserId(), mSessionCallback);
+ mUserStartedCallback.onUserStarted(getTargetUserId(), newSession);
+ getCallback().onClientFinished(this, true /* success */);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
new file mode 100644
index 0000000..ba81357
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintStopUserClient.java
@@ -0,0 +1,58 @@
+/*
+ * 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.biometrics.sensors.fingerprint.aidl;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.biometrics.fingerprint.ISession;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.biometrics.sensors.StopUserClient;
+
+public class FingerprintStopUserClient extends StopUserClient<ISession> {
+ private static final String TAG = "FingerprintStopUserClient";
+
+ public FingerprintStopUserClient(@NonNull Context context,
+ @NonNull LazyDaemon<ISession> lazyDaemon, @Nullable IBinder token, int userId,
+ int sensorId, @NonNull UserStoppedCallback callback) {
+ super(context, lazyDaemon, token, userId, sensorId, callback);
+ }
+
+ @Override
+ public void start(@NonNull Callback callback) {
+ super.start(callback);
+ startHalOperation();
+ }
+
+ @Override
+ protected void startHalOperation() {
+ try {
+ getFreshDaemon().close(mSequentialId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ getCallback().onClientFinished(this, false /* success */);
+ }
+ }
+
+ @Override
+ public void unableToStart() {
+
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
index d843bc9..cd12d02 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/Sensor.java
@@ -24,15 +24,17 @@
import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.fingerprint.Error;
-import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.ISession;
import android.hardware.biometrics.fingerprint.ISessionCallback;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.keymaster.HardwareAuthToken;
+import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -53,6 +55,9 @@
import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.RemovalConsumer;
+import com.android.server.biometrics.sensors.StartUserClient;
+import com.android.server.biometrics.sensors.StopUserClient;
+import com.android.server.biometrics.sensors.UserAwareBiometricScheduler;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
@@ -72,9 +77,10 @@
@NonNull private final String mTag;
@NonNull private final FingerprintProvider mProvider;
@NonNull private final Context mContext;
+ @NonNull private final IBinder mToken;
@NonNull private final Handler mHandler;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
@NonNull private final LockoutCache mLockoutCache;
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@@ -112,13 +118,13 @@
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final String mTag;
- @NonNull private final BiometricScheduler mScheduler;
+ @NonNull private final UserAwareBiometricScheduler mScheduler;
private final int mSensorId;
private final int mUserId;
@NonNull private final Callback mCallback;
HalSessionCallback(@NonNull Context context, @NonNull Handler handler, @NonNull String tag,
- @NonNull BiometricScheduler scheduler, int sensorId, int userId,
+ @NonNull UserAwareBiometricScheduler scheduler, int sensorId, int userId,
@NonNull Callback callback) {
mContext = context;
mHandler = handler;
@@ -406,9 +412,7 @@
@Override
public void onSessionClosed() {
- mHandler.post(() -> {
- // TODO: implement this.
- });
+ mHandler.post(mScheduler::onUserStopped);
}
}
@@ -418,9 +422,53 @@
mTag = tag;
mProvider = provider;
mContext = context;
+ mToken = new Binder();
mHandler = handler;
mSensorProperties = sensorProperties;
- mScheduler = new BiometricScheduler(tag, gestureAvailabilityDispatcher);
+ mScheduler = new UserAwareBiometricScheduler(tag, gestureAvailabilityDispatcher,
+ () -> mCurrentSession != null ? mCurrentSession.mUserId : UserHandle.USER_NULL,
+ new UserAwareBiometricScheduler.UserSwitchCallback() {
+ @NonNull
+ @Override
+ public StopUserClient<?> getStopUserClient(int userId) {
+ return new FingerprintStopUserClient(mContext, mLazySession, mToken,
+ userId, mSensorProperties.sensorId, () -> mCurrentSession = null);
+ }
+
+ @NonNull
+ @Override
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
+ final HalSessionCallback.Callback callback = () -> {
+ Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
+ mCurrentSession = null;
+ };
+
+ final int sensorId = mSensorProperties.sensorId;
+
+ final HalSessionCallback resultController = new HalSessionCallback(mContext,
+ mHandler, mTag, mScheduler, sensorId, newUserId, callback);
+
+ final StartUserClient.UserStartedCallback<ISession> userStartedCallback =
+ (userIdStarted, newSession) -> {
+ mCurrentSession = new Session(mTag,
+ newSession, userIdStarted, resultController);
+ if (FingerprintUtils.getInstance(sensorId)
+ .isInvalidationInProgress(mContext, userIdStarted)) {
+ Slog.w(mTag,
+ "Scheduling unfinished invalidation request for "
+ + "sensor: "
+ + sensorId
+ + ", user: " + userIdStarted);
+ provider.scheduleInvalidationRequest(sensorId,
+ userIdStarted);
+ }
+ };
+
+ return new FingerprintStartUserClient(mContext, provider::getHalInstance,
+ mToken, newUserId, mSensorProperties.sensorId,
+ resultController, userStartedCallback);
+ }
+ });
mLockoutCache = new LockoutCache();
mAuthenticatorIds = new HashMap<>();
mLazySession = () -> mCurrentSession != null ? mCurrentSession.mSession : null;
@@ -434,11 +482,6 @@
return mSensorProperties;
}
- @SuppressWarnings("BooleanMethodIsAlwaysInverted")
- boolean hasSessionForUser(int userId) {
- return mCurrentSession != null && mCurrentSession.mUserId == userId;
- }
-
@Nullable Session getSessionForUser(int userId) {
if (mCurrentSession != null && mCurrentSession.mUserId == userId) {
return mCurrentSession;
@@ -452,20 +495,6 @@
mProvider, this);
}
- void createNewSession(@NonNull IFingerprint daemon, int sensorId, int userId)
- throws RemoteException {
-
- final HalSessionCallback.Callback callback = () -> {
- Slog.e(mTag, "Got ERROR_HW_UNAVAILABLE");
- mCurrentSession = null;
- };
- final HalSessionCallback resultController = new HalSessionCallback(mContext, mHandler,
- mTag, mScheduler, sensorId, userId, callback);
-
- final ISession newSession = daemon.createSession(sensorId, userId, resultController);
- mCurrentSession = new Session(mTag, newSession, userId, resultController);
- }
-
@NonNull BiometricScheduler getScheduler() {
return mScheduler;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
index 0b7f3ab..31fc068 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/TestHal.java
@@ -121,7 +121,8 @@
@Override
public void close(int cookie) throws RemoteException {
- cb.onStateChanged(cookie, SessionState.CLOSED);
+ Slog.w(TAG, "close, cookie: " + cookie);
+ cb.onSessionClosed();
}
@Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 41bc0b9..874e9a6 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1054,27 +1054,16 @@
clipboard.mNotifiedUids.put(uid, true);
Binder.withCleanCallingIdentity(() -> {
- // Retrieve the app label of the source of the clip data
- CharSequence sourceAppLabel = null;
- if (clipboard.mPrimaryClipPackage != null) {
- try {
- sourceAppLabel = mPm.getApplicationLabel(mPm.getApplicationInfoAsUser(
- clipboard.mPrimaryClipPackage, 0, userId));
- } catch (PackageManager.NameNotFoundException e) {
- // leave label as null
- }
- }
-
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
String message;
- if (sourceAppLabel != null) {
+ if (isText(clipboard.primaryClip)) {
message = getContext().getString(
- R.string.pasted_from_app, callingAppLabel, sourceAppLabel);
+ R.string.pasted_text, callingAppLabel);
} else {
message = getContext().getString(
- R.string.pasted_from_clipboard, callingAppLabel);
+ R.string.pasted_content, callingAppLabel);
}
Slog.i(TAG, message);
Toast.makeText(
@@ -1085,4 +1074,21 @@
}
});
}
+
+ /**
+ * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
+ * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
+ * no URI or Intent. Note that HTML may be provided along with text so the presence of
+ * HtmlText in the clip does not prevent this method returning true.
+ */
+ private static boolean isText(@NonNull ClipData data) {
+ if (data.getItemCount() > 1) {
+ return false;
+ }
+ ClipData.Item item = data.getItemAt(0);
+
+ return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
+ && item.getIntent() == null;
+ }
+
}
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/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f3d2012..6ea84ce 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -18,12 +18,14 @@
import static android.util.TimeUtils.NANOS_PER_MS;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.INetdEventCallback;
import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.net.metrics.ConnectStats;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
@@ -98,6 +100,7 @@
private final TokenBucket mConnectTb =
new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
+ final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback();
/**
* There are only 3 possible callbacks.
@@ -158,6 +161,9 @@
public NetdEventListenerService(ConnectivityManager cm) {
// We are started when boot is complete, so ConnectivityService should already be running.
mCm = cm;
+ // Clear all capabilities to listen all networks.
+ mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(),
+ mCallback);
}
private static long projectSnapshotTime(long timeMs) {
@@ -389,18 +395,13 @@
}
private long getTransports(int netId) {
- // TODO: directly query ConnectivityService instead of going through Binder interface.
- NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
+ final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
if (nc == null) {
return 0;
}
return BitUtils.packBits(nc.getTransportTypes());
}
- private static void maybeLog(String s, Object... args) {
- if (DBG) Log.d(TAG, String.format(s, args));
- }
-
/** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
static class NetworkMetricsSnapshot {
@@ -428,4 +429,29 @@
return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
}
}
+
+ private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
+
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ synchronized (mCapabilities) {
+ mCapabilities.put(network.getNetId(), nc);
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ synchronized (mCapabilities) {
+ mCapabilities.remove(network.getNetId());
+ }
+ }
+
+ @Nullable
+ public NetworkCapabilities getNetworkCapabilities(int netId) {
+ synchronized (mCapabilities) {
+ return mCapabilities.get(netId);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 103ab95..a8cbcb5 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -49,6 +49,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -633,7 +634,13 @@
@Override
public void sendEpsQosSessionAvailable(final int qosCallbackId, final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
- mQosCallbackTracker.sendEventQosSessionAvailable(qosCallbackId, session, attributes);
+ mQosCallbackTracker.sendEventEpsQosSessionAvailable(qosCallbackId, session, attributes);
+ }
+
+ @Override
+ public void sendNrQosSessionAvailable(final int qosCallbackId, final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ mQosCallbackTracker.sendEventNrQosSessionAvailable(qosCallbackId, session, attributes);
}
@Override
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/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 488677a..3711679 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -271,6 +271,13 @@
return mApps.containsKey(uid);
}
+ /**
+ * Returns whether the given uid has permission to use restricted networks.
+ */
+ public synchronized boolean hasRestrictedNetworksPermission(int uid) {
+ return Boolean.TRUE.equals(mApps.get(uid));
+ }
+
private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
List<Integer> network = new ArrayList<>();
List<Integer> system = new ArrayList<>();
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
index 0f5400d..534dbe7 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackAgentConnection.java
@@ -27,6 +27,7 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import java.util.Objects;
@@ -146,13 +147,23 @@
mNetworkAgentInfo.onQosCallbackUnregistered(mAgentCallbackId);
}
- void sendEventQosSessionAvailable(final QosSession session,
+ void sendEventEpsQosSessionAvailable(final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
try {
- if (DBG) log("sendEventQosSessionAvailable: sending...");
+ if (DBG) log("sendEventEpsQosSessionAvailable: sending...");
mCallback.onQosEpsBearerSessionAvailable(session, attributes);
} catch (final RemoteException e) {
- loge("sendEventQosSessionAvailable: remote exception", e);
+ loge("sendEventEpsQosSessionAvailable: remote exception", e);
+ }
+ }
+
+ void sendEventNrQosSessionAvailable(final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ try {
+ if (DBG) log("sendEventNrQosSessionAvailable: sending...");
+ mCallback.onNrQosSessionAvailable(session, attributes);
+ } catch (final RemoteException e) {
+ loge("sendEventNrQosSessionAvailable: remote exception", e);
}
}
diff --git a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
index 8bda532..b6ab47b 100644
--- a/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
+++ b/services/core/java/com/android/server/connectivity/QosCallbackTracker.java
@@ -27,6 +27,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.util.Log;
import com.android.net.module.util.CollectionUtils;
@@ -179,17 +180,31 @@
}
/**
- * Called when the NetworkAgent sends the qos session available event
+ * Called when the NetworkAgent sends the qos session available event for EPS
*
* @param qosCallbackId the callback id that the qos session is now available to
* @param session the qos session that is now available
* @param attributes the qos attributes that are now available on the qos session
*/
- public void sendEventQosSessionAvailable(final int qosCallbackId,
+ public void sendEventEpsQosSessionAvailable(final int qosCallbackId,
final QosSession session,
final EpsBearerQosSessionAttributes attributes) {
- runOnAgentConnection(qosCallbackId, "sendEventQosSessionAvailable: ",
- ac -> ac.sendEventQosSessionAvailable(session, attributes));
+ runOnAgentConnection(qosCallbackId, "sendEventEpsQosSessionAvailable: ",
+ ac -> ac.sendEventEpsQosSessionAvailable(session, attributes));
+ }
+
+ /**
+ * Called when the NetworkAgent sends the qos session available event for NR
+ *
+ * @param qosCallbackId the callback id that the qos session is now available to
+ * @param session the qos session that is now available
+ * @param attributes the qos attributes that are now available on the qos session
+ */
+ public void sendEventNrQosSessionAvailable(final int qosCallbackId,
+ final QosSession session,
+ final NrQosSessionAttributes attributes) {
+ runOnAgentConnection(qosCallbackId, "sendEventNrQosSessionAvailable: ",
+ ac -> ac.sendEventNrQosSessionAvailable(session, attributes));
}
/**
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bd9a520..a070f27 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1133,6 +1133,8 @@
* @return a Network if there is a running VPN network or null if there is no running VPN
* network or network is null.
*/
+ @VisibleForTesting
+ @Nullable
public synchronized Network getNetwork() {
final NetworkAgent agent = mNetworkAgent;
if (null == agent) return null;
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 4f95d27..7518130 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -41,11 +41,11 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Supplier;
/**
* Manages set of updatable font files.
@@ -109,6 +109,7 @@
private final FsverityUtil mFsverityUtil;
private final File mConfigFile;
private final File mTmpConfigFile;
+ private final Supplier<Long> mCurrentTimeSupplier;
private long mLastModifiedMillis;
private int mConfigVersion = 1;
@@ -128,18 +129,20 @@
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
FsverityUtil fsverityUtil) {
- this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE));
+ this(filesDir, preinstalledFontDirs, parser, fsverityUtil, new File(CONFIG_XML_FILE),
+ () -> System.currentTimeMillis());
}
// For unit testing
UpdatableFontDir(File filesDir, List<File> preinstalledFontDirs, FontFileParser parser,
- FsverityUtil fsverityUtil, File configFile) {
+ FsverityUtil fsverityUtil, File configFile, Supplier<Long> currentTimeSupplier) {
mFilesDir = filesDir;
mPreinstalledFontDirs = preinstalledFontDirs;
mParser = parser;
mFsverityUtil = fsverityUtil;
mConfigFile = configFile;
mTmpConfigFile = new File(configFile.getAbsoluteFile() + ".tmp");
+ mCurrentTimeSupplier = currentTimeSupplier;
}
/* package */ void loadFontFileMap() {
@@ -209,7 +212,7 @@
FileUtils.deleteContents(mFilesDir);
mFontFamilyMap.clear();
- mLastModifiedMillis = System.currentTimeMillis();
+ mLastModifiedMillis = mCurrentTimeSupplier.get();
try (FileOutputStream fos = new FileOutputStream(mConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
@@ -245,7 +248,7 @@
}
// Write config file.
- mLastModifiedMillis = Instant.now().getEpochSecond();
+ mLastModifiedMillis = mCurrentTimeSupplier.get();
try (FileOutputStream fos = new FileOutputStream(mTmpConfigFile)) {
PersistentSystemFontConfig.writeToXml(fos, createPersistentConfig());
} catch (Exception e) {
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/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index db2e908..a30d993 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -1225,6 +1225,70 @@
}
@Override
+ public MediaSession.Token getMediaKeyEventSession() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " get the media key event session");
+ }
+ MediaSessionRecordImpl record;
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "No matching user record to get the media key event session"
+ + ", userId=" + userId);
+ return null;
+ }
+ record = user.getMediaButtonSessionLocked();
+ }
+ if (record instanceof MediaSessionRecord) {
+ return ((MediaSessionRecord) record).getSessionToken();
+ }
+ //TODO: Handle media session 2 case
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public String getMediaKeyEventSessionPackageName() {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (!hasMediaControlPermission(pid, uid)) {
+ throw new SecurityException("MEDIA_CONTENT_CONTROL permission is required to"
+ + " get the media key event session package");
+ }
+ MediaSessionRecordImpl record;
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "No matching user record to get the media key event session"
+ + " package , userId=" + userId);
+ return "";
+ }
+ record = user.getMediaButtonSessionLocked();
+ if (record instanceof MediaSessionRecord) {
+ return record.getPackageName();
+ //TODO: Handle media session 2 case
+ } else if (user.mLastMediaButtonReceiverHolder != null) {
+ return user.mLastMediaButtonReceiverHolder.getPackageName();
+ }
+ }
+ return "";
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
public void addSessionsListener(IActiveSessionsListener listener,
ComponentName componentName, int userId) throws RemoteException {
final int pid = Binder.getCallingPid();
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 39ed7e8..2e4d41c 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -96,9 +96,10 @@
/**
* Notifies that the specified {@link NetworkStatsProvider} has reached its quota
- * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)}.
+ * which was set through {@link NetworkStatsProvider#onSetLimit(String, long)} or
+ * {@link NetworkStatsProvider#onSetWarningAndLimit(String, long, long)}.
*
* @param tag the human readable identifier of the custom network stats provider.
*/
- public abstract void onStatsProviderLimitReached(@NonNull String tag);
+ public abstract void onStatsProviderWarningOrLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 16eac91..3859285 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -40,6 +40,14 @@
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
+import static android.net.ConnectivityManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
@@ -68,15 +76,7 @@
import static android.net.NetworkPolicyManager.ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS;
import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_MASK;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_APP_STANDBY;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_DOZE;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_RESTRICTED_MODE;
import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
@@ -422,15 +422,15 @@
private static final int MSG_LIMIT_REACHED = 5;
private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6;
private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7;
- private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
- private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
+ private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10;
+ private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11;
private static final int MSG_POLICIES_CHANGED = 13;
private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
- private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
+ private static final int MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED = 20;
// TODO: Add similar docs for other messages.
/**
* Message to indicate that reasons for why an uid is blocked changed.
@@ -2034,39 +2034,45 @@
final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
- if (hasLimit || policy.metered) {
- final long quotaBytes;
- if (hasLimit && policy.hasCycle()) {
- final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
- .cycleIterator(policy).next();
- final long start = cycle.first.toInstant().toEpochMilli();
- final long end = cycle.second.toInstant().toEpochMilli();
- final long totalBytes = getTotalBytes(policy.template, start, end);
+ long limitBytes = Long.MAX_VALUE;
+ long warningBytes = Long.MAX_VALUE;
+ if ((hasLimit || hasWarning) && policy.hasCycle()) {
+ final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
+ .cycleIterator(policy).next();
+ final long start = cycle.first.toInstant().toEpochMilli();
+ final long end = cycle.second.toInstant().toEpochMilli();
+ final long totalBytes = getTotalBytes(policy.template, start, end);
- if (policy.lastLimitSnooze >= start) {
- // snoozing past quota, but we still need to restrict apps,
- // so push really high quota.
- quotaBytes = Long.MAX_VALUE;
- } else {
- // remaining "quota" bytes are based on total usage in
- // current cycle. kernel doesn't like 0-byte rules, so we
- // set 1-byte quota and disable the radio later.
- quotaBytes = Math.max(1, policy.limitBytes - totalBytes);
- }
- } else {
- // metered network, but no policy limit; we still need to
- // restrict apps, so push really high quota.
- quotaBytes = Long.MAX_VALUE;
+ // If the limit notification is not snoozed, the limit quota needs to be calculated.
+ if (hasLimit && policy.lastLimitSnooze < start) {
+ // remaining "quota" bytes are based on total usage in
+ // current cycle. kernel doesn't like 0-byte rules, so we
+ // set 1-byte quota and disable the radio later.
+ limitBytes = Math.max(1, policy.limitBytes - totalBytes);
}
+ // If the warning notification was snoozed by user, or the service already knows
+ // it is over warning bytes, doesn't need to calculate warning bytes.
+ if (hasWarning && policy.lastWarningSnooze < start
+ && !policy.isOverWarning(totalBytes)) {
+ warningBytes = Math.max(1, policy.warningBytes - totalBytes);
+ }
+ }
+
+ if (hasWarning || hasLimit || policy.metered) {
if (matchingIfaces.size() > 1) {
// TODO: switch to shared quota once NMS supports
Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
}
+ // Set the interface warning and limit. For interfaces which has no cycle,
+ // or metered with no policy quotas, or snoozed notification; we still need to put
+ // iptables rule hooks to restrict apps for data saver, so push really high quota.
+ // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to
+ // providers.
for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
final String iface = matchingIfaces.valueAt(j);
- setInterfaceQuotaAsync(iface, quotaBytes);
+ setInterfaceQuotasAsync(iface, warningBytes, limitBytes);
newMeteredIfaces.add(iface);
}
}
@@ -2089,7 +2095,7 @@
for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
final String iface = matchingIfaces.valueAt(j);
if (!newMeteredIfaces.contains(iface)) {
- setInterfaceQuotaAsync(iface, Long.MAX_VALUE);
+ setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE);
newMeteredIfaces.add(iface);
}
}
@@ -2101,7 +2107,7 @@
for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) {
final String iface = mMeteredIfaces.valueAt(i);
if (!newMeteredIfaces.contains(iface)) {
- removeInterfaceQuotaAsync(iface);
+ removeInterfaceQuotasAsync(iface);
}
}
mMeteredIfaces = newMeteredIfaces;
@@ -4970,7 +4976,7 @@
mListeners.finishBroadcast();
return true;
}
- case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ case MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED: {
mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
@@ -5041,19 +5047,20 @@
mNetworkStats.advisePersistThreshold(persistThreshold);
return true;
}
- case MSG_UPDATE_INTERFACE_QUOTA: {
- final String iface = (String) msg.obj;
- // int params need to be stitched back into a long
- final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
- removeInterfaceQuota(iface);
- setInterfaceQuota(iface, quota);
- mNetworkStats.setStatsProviderLimitAsync(iface, quota);
+ case MSG_UPDATE_INTERFACE_QUOTAS: {
+ final IfaceQuotas val = (IfaceQuotas) msg.obj;
+ // TODO: Consider set a new limit before removing the original one.
+ removeInterfaceLimit(val.iface);
+ setInterfaceLimit(val.iface, val.limit);
+ mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning,
+ val.limit);
return true;
}
- case MSG_REMOVE_INTERFACE_QUOTA: {
+ case MSG_REMOVE_INTERFACE_QUOTAS: {
final String iface = (String) msg.obj;
- removeInterfaceQuota(iface);
- mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED);
+ removeInterfaceLimit(iface);
+ mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED,
+ QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5201,15 +5208,32 @@
}
}
- private void setInterfaceQuotaAsync(String iface, long quotaBytes) {
- // long quotaBytes split up into two ints to fit in message
- mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32),
- (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget();
+ private static final class IfaceQuotas {
+ @NonNull public final String iface;
+ // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE
+ // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules.
+ public final long warning;
+ public final long limit;
+
+ private IfaceQuotas(@NonNull String iface, long warning, long limit) {
+ this.iface = iface;
+ this.warning = warning;
+ this.limit = limit;
+ }
}
- private void setInterfaceQuota(String iface, long quotaBytes) {
+ private void setInterfaceQuotasAsync(@NonNull String iface,
+ long warningBytes, long limitBytes) {
+ mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS,
+ new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget();
+ }
+
+ private void setInterfaceLimit(String iface, long limitBytes) {
try {
- mNetworkManager.setInterfaceQuota(iface, quotaBytes);
+ // For legacy design the data warning is covered by global alert, where the
+ // kernel will notify upper layer for a small amount of change of traffic
+ // statistics. Thus, passing warning is not needed.
+ mNetworkManager.setInterfaceQuota(iface, limitBytes);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting interface quota", e);
} catch (RemoteException e) {
@@ -5217,11 +5241,11 @@
}
}
- private void removeInterfaceQuotaAsync(String iface) {
- mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget();
+ private void removeInterfaceQuotasAsync(String iface) {
+ mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget();
}
- private void removeInterfaceQuota(String iface) {
+ private void removeInterfaceLimit(String iface) {
try {
mNetworkManager.removeInterfaceQuota(iface);
} catch (IllegalStateException e) {
@@ -5726,9 +5750,9 @@
}
@Override
- public void onStatsProviderLimitReached(@NonNull String tag) {
- Log.v(TAG, "onStatsProviderLimitReached: " + tag);
- mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+ public void onStatsProviderWarningOrLimitReached(@NonNull String tag) {
+ Log.v(TAG, "onStatsProviderWarningOrLimitReached: " + tag);
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED).sendToTarget();
}
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 0cb0bc2c..0e9a9da 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -37,8 +37,9 @@
public abstract void forceUpdate();
/**
- * Set the quota limit to all registered custom network stats providers.
+ * Set the warning and limit to all registered custom network stats providers.
* Note that invocation of any interface will be sent to all providers.
*/
- public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota);
+ public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+ long limit);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index fe43c31..de5aae0 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1674,9 +1674,14 @@
}
@Override
- public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
- if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
- invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetLimit(iface, quota));
+ public void setStatsProviderWarningAndLimitAsync(
+ @NonNull String iface, long warning, long limit) {
+ if (LOGV) {
+ Slog.v(TAG, "setStatsProviderWarningAndLimitAsync("
+ + iface + "," + warning + "," + limit + ")");
+ }
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
+ warning, limit));
}
}
@@ -2071,10 +2076,10 @@
}
@Override
- public void notifyLimitReached() {
- Log.d(TAG, mTag + ": onLimitReached");
+ public void notifyWarningOrLimitReached() {
+ Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
LocalServices.getService(NetworkPolicyManagerInternal.class)
- .onStatsProviderLimitReached(mTag);
+ .onStatsProviderWarningOrLimitReached(mTag);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b27c0bd..764fa02 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -252,7 +252,6 @@
import android.content.pm.parsing.component.ParsedProcess;
import android.content.pm.parsing.component.ParsedProvider;
import android.content.pm.parsing.component.ParsedService;
-import android.content.pm.parsing.component.ParsedUsesPermission;
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.content.res.Resources;
@@ -371,6 +370,7 @@
import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
import com.android.server.compat.CompatChange;
import com.android.server.compat.PlatformCompat;
import com.android.server.net.NetworkPolicyManagerInternal;
@@ -778,16 +778,6 @@
private static final String COMPANION_PACKAGE_NAME = "com.android.companiondevicemanager";
- /** Canonical intent used to identify what counts as a "web browser" app */
- private static final Intent sBrowserIntent;
- static {
- sBrowserIntent = new Intent();
- sBrowserIntent.setAction(Intent.ACTION_VIEW);
- sBrowserIntent.addCategory(Intent.CATEGORY_BROWSABLE);
- sBrowserIntent.setData(Uri.parse("http:"));
- sBrowserIntent.addFlags(Intent.FLAG_IGNORE_EPHEMERAL);
- }
-
// Compilation reasons.
public static final int REASON_UNKNOWN = -1;
public static final int REASON_FIRST_BOOT = 0;
@@ -5636,7 +5626,7 @@
// Work that needs to happen on first install within each user
if (firstUserIds != null && firstUserIds.length > 0) {
for (int userId : firstUserIds) {
- clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
pkgSetting.getInstallReason(userId), userId);
}
}
@@ -7752,19 +7742,6 @@
return matches.get(0).getComponentInfo().getComponentName();
}
- private boolean packageIsBrowser(String packageName, int userId) {
- List<ResolveInfo> list = queryIntentActivitiesInternal(sBrowserIntent, null,
- PackageManager.MATCH_ALL, userId);
- final int N = list.size();
- for (int i = 0; i < N; i++) {
- ResolveInfo info = list.get(i);
- if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
- return true;
- }
- }
- return false;
- }
-
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -12304,9 +12281,15 @@
public ArraySet<String> getOptimizablePackages() {
ArraySet<String> pkgs = new ArraySet<>();
+ final boolean hibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+ AppHibernationManagerInternal appHibernationManager =
+ mInjector.getLocalService(AppHibernationManagerInternal.class);
synchronized (mLock) {
for (AndroidPackage p : mPackages.values()) {
- if (PackageDexOptimizer.canOptimizePackage(p)) {
+ // Checking hibernation state is an inexpensive call.
+ boolean isHibernating = hibernationEnabled
+ && appHibernationManager.isHibernatingGlobally(p.getPackageName());
+ if (PackageDexOptimizer.canOptimizePackage(p) && !isHibernating) {
pkgs.add(p.getPackageName());
}
}
@@ -15459,16 +15442,55 @@
null);
}
- private void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
boolean suspended) {
- final Bundle extras = new Bundle(3);
- extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
- extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
- sendPackageBroadcast(
- suspended ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED,
- null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- new int[] {userId}, null, null, null);
+ final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
+ final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
+ final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
+ final int[] userIds = new int[] {userId};
+ // Get allow lists for the pkg in the pkgList. Merge into the existed pkgs and uids if
+ // allow lists are the same.
+ synchronized (mLock) {
+ for (int i = 0; i < pkgList.length; i++) {
+ final String pkgName = pkgList[i];
+ final int uid = uidList[i];
+ SparseArray<int[]> allowList = mAppsFilter.getVisibilityAllowList(
+ getPackageSettingInternal(pkgName, Process.SYSTEM_UID),
+ userIds, mSettings.getPackagesLocked());
+ if (allowList == null) {
+ allowList = new SparseArray<>(0);
+ }
+ boolean merged = false;
+ for (int j = 0; j < allowListsToSend.size(); j++) {
+ if (Arrays.equals(allowListsToSend.get(j).get(userId), allowList.get(userId))) {
+ pkgsToSend.get(j).add(pkgName);
+ uidsToSend.get(j).add(uid);
+ merged = true;
+ break;
+ }
+ }
+ if (!merged) {
+ pkgsToSend.add(new ArrayList<>(Arrays.asList(pkgName)));
+ uidsToSend.add(IntArray.wrap(new int[] {uid}));
+ allowListsToSend.add(allowList);
+ }
+ }
+ }
+
+ for (int i = 0; i < pkgsToSend.size(); i++) {
+ final Bundle extras = new Bundle(3);
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+ pkgsToSend.get(i).toArray(new String[pkgsToSend.get(i).size()]));
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
+ final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
+ ? null : allowListsToSend.get(i);
+ sendPackageBroadcast(
+ suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED,
+ null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
+ userIds, null, allowList, null);
+ }
}
/**
@@ -15612,7 +15634,7 @@
PostInstallData postInstallData =
new PostInstallData(null, res, () -> {
- clearRolesAndRestorePermissionsForNewUserInstall(packageName,
+ restorePermissionsAndUpdateRolesForNewUserInstall(packageName,
pkgSetting.getInstallReason(userId), userId);
if (intentSender != null) {
onRestoreComplete(res.returnCode, mContext, intentSender);
@@ -21182,7 +21204,6 @@
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
mDomainVerificationManager.clearPackage(deletedPs.name);
- clearDefaultBrowserIfNeeded(packageName);
mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
mAppsFilter.removePackage(getPackageSetting(packageName));
removedAppId = mSettings.removePackageLPw(packageName);
@@ -21740,7 +21761,6 @@
destroyAppDataLIF(pkg, nextUserId,
FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
}
- clearDefaultBrowserIfNeededForUser(ps.name, nextUserId);
removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), nextUserId, ps.appId);
clearPackagePreferredActivities(ps.name, nextUserId);
mPermissionManager.onPackageUninstalled(ps.name, ps.appId, pkg, sharedUserPkgs,
@@ -22257,36 +22277,8 @@
mSettings.clearPackagePreferredActivities(packageName, outUserChanged, userId);
}
- /** Clears state for all users, and touches intent filter verification policy */
- void clearDefaultBrowserIfNeeded(String packageName) {
- for (int oneUserId : mUserManager.getUserIds()) {
- clearDefaultBrowserIfNeededForUser(packageName, oneUserId);
- }
- }
-
- private void clearDefaultBrowserIfNeededForUser(String packageName, int userId) {
- final String defaultBrowserPackageName = mDefaultAppProvider.getDefaultBrowser(userId);
- if (!TextUtils.isEmpty(defaultBrowserPackageName)) {
- if (packageName.equals(defaultBrowserPackageName)) {
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
- }
- }
- }
-
- private void clearRolesAndRestorePermissionsForNewUserInstall(String packageName,
+ private void restorePermissionsAndUpdateRolesForNewUserInstall(String packageName,
int installReason, @UserIdInt int userId) {
- // If this app is a browser and it's newly-installed for some
- // users, clear any default-browser state in those users. The
- // app's nature doesn't depend on the user, so we can just check
- // its browser nature in any user and generalize.
- if (packageIsBrowser(packageName, userId)) {
- // If this browser is restored from user's backup, do not clear
- // default-browser state for this user
- if (installReason != PackageManager.INSTALL_REASON_DEVICE_RESTORE) {
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
- }
- }
-
// We may also need to apply pending (restored) runtime permission grants
// within these users.
mPermissionManager.restoreDelayedRuntimePermissions(packageName, userId);
@@ -22320,11 +22312,6 @@
}
}
updateDefaultHomeNotLocked(userId);
- // TODO: We have to reset the default SMS and Phone. This requires
- // significant refactoring to keep all default apps in the package
- // manager (cleaner but more work) or have the services provide
- // callbacks to the package manager to request a default app reset.
- mDefaultAppProvider.setDefaultBrowser(null, true, userId);
resetNetworkPolicies(userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
@@ -27329,6 +27316,11 @@
return PackageManagerService.this.getPackageStartability(
packageName, callingUid, userId) == PACKAGE_STARTABILITY_FROZEN;
}
+
+ @Override
+ public void deleteOatArtifactsOfPackage(String packageName) {
+ PackageManagerService.this.deleteOatArtifactsOfPackage(packageName);
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index b5765b5..0ddb6cd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -104,8 +104,8 @@
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.PackageManagerShellCommandDataLoader.Metadata;
-import com.android.server.pm.verify.domain.DomainVerificationShell;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.verify.domain.DomainVerificationShell;
import dalvik.system.DexFile;
@@ -2251,8 +2251,8 @@
}
}
- final String packageName = getNextArg();
- if (packageName == null) {
+ final List<String> packageNames = getRemainingArgs();
+ if (packageNames.isEmpty()) {
pw.println("Error: package name not specified");
return 1;
}
@@ -2270,12 +2270,15 @@
try {
final int translatedUserId =
translateUserId(userId, UserHandle.USER_NULL, "runSuspend");
- mInterface.setPackagesSuspendedAsUser(new String[]{packageName}, suspendedState,
- ((appExtras.size() > 0) ? appExtras : null),
+ mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
+ suspendedState, ((appExtras.size() > 0) ? appExtras : null),
((launcherExtras.size() > 0) ? launcherExtras : null),
info, callingPackage, translatedUserId);
- pw.println("Package " + packageName + " new suspended state: "
- + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+ for (int i = 0; i < packageNames.size(); i++) {
+ final String packageName = packageNames.get(i);
+ pw.println("Package " + packageName + " new suspended state: "
+ + mInterface.isPackageSuspendedForUser(packageName, translatedUserId));
+ }
return 0;
} catch (RemoteException | IllegalArgumentException e) {
pw.println(e.toString());
@@ -3643,11 +3646,11 @@
pw.println(" hide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println(" unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
pw.println("");
- pw.println(" suspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Suspends the specified package (as user).");
+ pw.println(" suspend [--user USER_ID] PACKAGE [PACKAGE...]");
+ pw.println(" Suspends the specified package(s) (as user).");
pw.println("");
- pw.println(" unsuspend [--user USER_ID] TARGET-PACKAGE");
- pw.println(" Unsuspends the specified package (as user).");
+ pw.println(" unsuspend [--user USER_ID] PACKAGE [PACKAGE...]");
+ pw.println(" Unsuspends the specified package(s) (as user).");
pw.println("");
pw.println(" grant [--user USER_ID] PACKAGE PERMISSION");
pw.println(" revoke [--user USER_ID] PACKAGE PERMISSION");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 3bb5c16..3244c44 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -35,6 +35,7 @@
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
@@ -1657,7 +1658,8 @@
| FLAG_PERMISSION_USER_FIXED
| FLAG_PERMISSION_REVOKED_COMPAT
| FLAG_PERMISSION_REVIEW_REQUIRED
- | FLAG_PERMISSION_ONE_TIME;
+ | FLAG_PERMISSION_ONE_TIME
+ | FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY;
final int policyOrSystemFlags = FLAG_PERMISSION_SYSTEM_FIXED
| FLAG_PERMISSION_POLICY_FIXED;
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 8075bdb..1b2ff08 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -148,6 +148,8 @@
@NonNull
private DomainVerificationProxy mProxy = new DomainVerificationProxyUnavailable();
+ private boolean mCanSendBroadcasts;
+
public DomainVerificationService(@NonNull Context context, @NonNull SystemConfig systemConfig,
@NonNull PlatformCompat platformCompat) {
super(context);
@@ -181,11 +183,18 @@
@Override
public void onBootPhase(int phase) {
super.onBootPhase(phase);
- if (phase != SystemService.PHASE_BOOT_COMPLETED || !hasRealVerifier()) {
+ if (!hasRealVerifier()) {
return;
}
- verifyPackages(null, false);
+ switch (phase) {
+ case PHASE_ACTIVITY_MANAGER_READY:
+ mCanSendBroadcasts = true;
+ break;
+ case PHASE_BOOT_COMPLETED:
+ verifyPackages(null, false);
+ break;
+ }
}
@Override
@@ -858,7 +867,7 @@
}
if (sendBroadcast) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -937,7 +946,7 @@
}
if (sendBroadcast && hasAutoVerifyDomains) {
- sendBroadcastForPackage(pkgName);
+ sendBroadcast(pkgName);
}
}
@@ -1098,8 +1107,19 @@
return mCollector;
}
- private void sendBroadcastForPackage(@NonNull String packageName) {
- mProxy.sendBroadcastForPackages(Collections.singleton(packageName));
+ private void sendBroadcast(@NonNull String packageName) {
+ sendBroadcast(Collections.singleton(packageName));
+ }
+
+ private void sendBroadcast(@NonNull Set<String> packageNames) {
+ if (!mCanSendBroadcasts) {
+ // If the system cannot send broadcasts, it's probably still in boot, so dropping this
+ // request should be fine. The verification agent should re-scan packages once boot
+ // completes.
+ return;
+ }
+
+ mProxy.sendBroadcastForPackages(packageNames);
}
private boolean hasRealVerifier() {
@@ -1183,7 +1203,7 @@
}
if (!packagesToBroadcast.isEmpty()) {
- mProxy.sendBroadcastForPackages(packagesToBroadcast);
+ sendBroadcast(packagesToBroadcast);
}
}
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 0a6772b..fe21201 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -734,7 +734,15 @@
return REBOOT_ERROR_SLOT_MISMATCH;
}
- if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+ final long origId = Binder.clearCallingIdentity();
+ boolean result;
+ try {
+ result = mInjector.getLockSettingsService().armRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ if (!result) {
Slog.w(TAG, "Failure to escrow key for reboot");
return REBOOT_ERROR_ARM_REBOOT_ESCROW_FAILURE;
}
@@ -742,11 +750,20 @@
return REBOOT_ERROR_NONE;
}
+ private boolean useServerBasedRoR() {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
+ "server_based_ror_enabled", false);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
private void reportMetricsOnRebootWithLskf(String packageName, boolean slotSwitch,
@ResumeOnRebootRebootErrorCode int errorCode) {
int uid = mInjector.getUidFromPackageName(packageName);
- boolean serverBased = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
- "server_based_ror_enabled", false);
+ boolean serverBased = useServerBasedRoR();
int preparedClientCount;
synchronized (this) {
preparedClientCount = mCallerPreparedForReboot.size();
diff --git a/services/core/java/com/android/server/wm/ActivityAssistInfo.java b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
new file mode 100644
index 0000000..054044b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityAssistInfo.java
@@ -0,0 +1,52 @@
+/*
+ * 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.wm;
+
+import android.os.IBinder;
+
+/**
+ * Class needed to expose some {@link ActivityRecord} fields in order to provide
+ * {@link android.service.voice.VoiceInteractionSession#onHandleAssist(AssistState)}
+ *
+ * @hide
+ */
+public class ActivityAssistInfo {
+ private final IBinder mActivityToken;
+ private final IBinder mAssistToken;
+ private final int mTaskId;
+
+ public ActivityAssistInfo(ActivityRecord activityRecord) {
+ this.mActivityToken = activityRecord.appToken;
+ this.mAssistToken = activityRecord.assistToken;
+ this.mTaskId = activityRecord.getTask().mTaskId;
+ }
+
+ /** @hide */
+ public IBinder getActivityToken() {
+ return mActivityToken;
+ }
+
+ /** @hide */
+ public IBinder getAssistToken() {
+ return mAssistToken;
+ }
+
+ /** @hide */
+ public int getTaskId() {
+ return mTaskId;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 12c67bb..c09136e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -32,7 +32,6 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.service.voice.IVoiceInteractionSession;
-import android.util.Pair;
import android.util.proto.ProtoOutputStream;
import android.window.TaskSnapshot;
@@ -166,7 +165,7 @@
* Returns the top activity from each of the currently visible root tasks, and the related task
* id. The first entry will be the focused activity.
*/
- public abstract List<Pair<IBinder, Integer>> getTopVisibleActivities();
+ public abstract List<ActivityAssistInfo> getTopVisibleActivities();
/**
* Returns whether {@code uid} has any resumed activity.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 29c5cec..c8fa50c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -215,7 +215,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -5112,7 +5111,7 @@
}
@Override
- public List<Pair<IBinder, Integer>> getTopVisibleActivities() {
+ public List<ActivityAssistInfo> getTopVisibleActivities() {
synchronized (mGlobalLock) {
return mRootWindowContainer.getTopVisibleActivities();
}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 857217f..7261048 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1817,8 +1817,8 @@
* @return a list of pairs, containing activities and their task id which are the top ones in
* each visible root task. The first entry will be the focused activity.
*/
- List<Pair<IBinder, Integer>> getTopVisibleActivities() {
- final ArrayList<Pair<IBinder, Integer>> topVisibleActivities = new ArrayList<>();
+ List<ActivityAssistInfo> getTopVisibleActivities() {
+ final ArrayList<ActivityAssistInfo> topVisibleActivities = new ArrayList<>();
final Task topFocusedRootTask = getTopDisplayFocusedRootTask();
// Traverse all displays.
forAllRootTasks(rootTask -> {
@@ -1826,8 +1826,7 @@
if (rootTask.shouldBeVisible(null /* starting */)) {
final ActivityRecord top = rootTask.getTopNonFinishingActivity();
if (top != null) {
- Pair<IBinder, Integer> visibleActivity = new Pair<>(top.appToken,
- top.getTask().mTaskId);
+ ActivityAssistInfo visibleActivity = new ActivityAssistInfo(top);
if (rootTask == topFocusedRootTask) {
topVisibleActivities.add(0, visibleActivity);
} else {
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/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 09ae8fc..44791b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10599,21 +10599,58 @@
});
}
+ /**
+ * Returns the apps that are non-exempt from some policies (such as suspension), and populates
+ * the given set with the apps that are exempt.
+ *
+ * @param packageNames apps to check
+ * @param outputExemptApps will be populate with subset of {@code packageNames} that is exempt
+ * from some policy restrictions
+ *
+ * @return subset of {@code packageNames} that is affected by some policy restrictions.
+ */
+ private String[] populateNonExemptAndExemptFromPolicyApps(String[] packageNames,
+ Set<String> outputExemptApps) {
+ Preconditions.checkArgument(outputExemptApps.isEmpty(), "outputExemptApps is not empty");
+ List<String> exemptApps = listPolicyExemptAppsUnchecked();
+ if (exemptApps.isEmpty()) {
+ return packageNames;
+ }
+ List<String> nonExemptApps = new ArrayList<>(packageNames.length);
+ for (int i = 0; i < packageNames.length; i++) {
+ String app = packageNames[i];
+ if (exemptApps.contains(app)) {
+ outputExemptApps.add(app);
+ } else {
+ nonExemptApps.add(app);
+ }
+ }
+ String[] result = new String[nonExemptApps.size()];
+ nonExemptApps.toArray(result);
+ return result;
+ }
+
@Override
public String[] setPackagesSuspended(ComponentName who, String callerPackage,
String[] packageNames, boolean suspended) {
+ Objects.requireNonNull(packageNames, "array of packages cannot be null");
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
Preconditions.checkCallAuthorization((caller.hasAdminComponent()
&& (isProfileOwner(caller) || isDeviceOwner(caller)))
|| (caller.hasPackage() && isCallerDelegate(caller, DELEGATION_PACKAGE_ACCESS)));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_PACKAGES_SUSPENDED);
- String[] result = null;
+ // Must remove the exempt apps from the input before calling PM, then add them back to
+ // the array returned to the caller
+ Set<String> exemptApps = new HashSet<>();
+ packageNames = populateNonExemptAndExemptFromPolicyApps(packageNames, exemptApps);
+
+ String[] nonSuspendedPackages = null;
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
- result = mIPackageManager.setPackagesSuspendedAsUser(packageNames, suspended, null,
- null, null, PLATFORM_PACKAGE_NAME, caller.getUserId());
+ nonSuspendedPackages = mIPackageManager.setPackagesSuspendedAsUser(packageNames,
+ suspended, null, null, null, PLATFORM_PACKAGE_NAME, caller.getUserId());
} catch (RemoteException re) {
// Shouldn't happen.
Slog.e(LOG_TAG, "Failed talking to the package manager", re);
@@ -10627,10 +10664,35 @@
.setBoolean(/* isDelegate */ who == null)
.setStrings(packageNames)
.write();
- if (result != null) {
- return result;
+
+ if (nonSuspendedPackages == null) {
+ Slog.w(LOG_TAG, "PM failed to suspend packages (%s)", Arrays.toString(packageNames));
+ return packageNames;
}
- return packageNames;
+ if (exemptApps.isEmpty()) {
+ return nonSuspendedPackages;
+ }
+
+ String[] result = buildNonSuspendedPackagesUnionArray(nonSuspendedPackages, exemptApps);
+ if (VERBOSE_LOG) Slog.v(LOG_TAG, "Returning %s", Arrays.toString(result));
+ return result;
+ }
+
+ /**
+ * Returns an array containing the union of the given non-suspended packages and
+ * exempt apps. Assumes both parameters are non-null and non-empty.
+ */
+ private String[] buildNonSuspendedPackagesUnionArray(String[] nonSuspendedPackages,
+ Set<String> exemptApps) {
+ String[] result = new String[nonSuspendedPackages.length + exemptApps.size()];
+ int index = 0;
+ for (String app : nonSuspendedPackages) {
+ result[index++] = app;
+ }
+ for (String app : exemptApps) {
+ result[index++] = app;
+ }
+ return result;
}
@Override
@@ -10656,9 +10718,15 @@
@Override
public List<String> listPolicyExemptApps() {
+ CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS));
+ hasCallingOrSelfPermission(permission.MANAGE_DEVICE_ADMINS) || isDeviceOwner(caller)
+ || isProfileOwner(caller));
+ return listPolicyExemptAppsUnchecked();
+ }
+
+ private List<String> listPolicyExemptAppsUnchecked() {
// TODO(b/181238156): decide whether it should only list the apps set by the resources,
// or also the "critical" apps defined by PersonalAppsSuspensionHelper (like SMS app).
// If it's the latter, refactor PersonalAppsSuspensionHelper so it (or a superclass) takes
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index bd3f99a..94f8e59 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);
@@ -308,6 +323,14 @@
}
}
+void IncrementalService::IncFsMount::setReadLogsRequested(bool value) {
+ if (value) {
+ flags |= StorageFlags::ReadLogsRequested;
+ } else {
+ flags &= ~StorageFlags::ReadLogsRequested;
+ }
+}
+
IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir)
: mVold(sm.getVoldService()),
mDataLoaderManager(sm.getDataLoaderManager()),
@@ -711,7 +734,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 +743,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) {
@@ -788,32 +812,38 @@
return -EINVAL;
}
- std::unique_lock l(ifs->lock);
- if (!enableReadLogs) {
- return disableReadLogsLocked(*ifs);
- }
+ std::string packageName;
- if (!ifs->readLogsAllowed()) {
- LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
- return -EPERM;
- }
+ {
+ std::unique_lock l(ifs->lock);
+ if (!enableReadLogs) {
+ return disableReadLogsLocked(*ifs);
+ }
- if (!ifs->dataLoaderStub) {
- // This should never happen - only DL can call enableReadLogs.
- LOG(ERROR) << "enableReadLogs failed: invalid state";
- return -EPERM;
- }
+ if (!ifs->readLogsAllowed()) {
+ LOG(ERROR) << "enableReadLogs failed, readlogs disallowed for storageId: " << storageId;
+ return -EPERM;
+ }
- // Check installation time.
- const auto now = mClock->now();
- const auto startLoadingTs = ifs->startLoadingTs;
- if (startLoadingTs <= now && now - startLoadingTs > Constants::readLogsMaxInterval) {
- LOG(ERROR) << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
- << storageId;
- return -EPERM;
- }
+ if (!ifs->dataLoaderStub) {
+ // This should never happen - only DL can call enableReadLogs.
+ LOG(ERROR) << "enableReadLogs failed: invalid state";
+ return -EPERM;
+ }
- const auto& packageName = ifs->dataLoaderStub->params().packageName;
+ // Check installation time.
+ const auto now = mClock->now();
+ const auto startLoadingTs = ifs->startLoadingTs;
+ if (startLoadingTs <= now && now - startLoadingTs > getReadLogsMaxInterval()) {
+ LOG(ERROR)
+ << "enableReadLogs failed, readlogs can't be enabled at this time, storageId: "
+ << storageId;
+ return -EPERM;
+ }
+
+ packageName = ifs->dataLoaderStub->params().packageName;
+ ifs->setReadLogsRequested(true);
+ }
// Check loader usage stats permission and apop.
if (auto status =
@@ -833,8 +863,14 @@
return fromBinderStatus(status);
}
- if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
- return status;
+ {
+ std::unique_lock l(ifs->lock);
+ if (!ifs->readLogsRequested()) {
+ return 0;
+ }
+ if (auto status = applyStorageParamsLocked(*ifs, /*enableReadLogs=*/true); status != 0) {
+ return status;
+ }
}
registerAppOpsCallback(packageName);
@@ -843,6 +879,7 @@
}
int IncrementalService::disableReadLogsLocked(IncFsMount& ifs) {
+ ifs.setReadLogsRequested(false);
return applyStorageParamsLocked(ifs, /*enableReadLogs=*/false);
}
@@ -1049,17 +1086,14 @@
return err;
}
if (params.size > 0) {
- // Only v2+ incfs supports automatically trimming file over-reserved sizes
- if (mIncFs->features() & incfs::Features::v2) {
- if (auto err = mIncFs->reserveSpace(ifs->control, normPath, params.size)) {
- if (err != -EOPNOTSUPP) {
- LOG(ERROR) << "Failed to reserve space for a new file: " << err;
- (void)mIncFs->unlink(ifs->control, normPath);
- return err;
- } else {
- LOG(WARNING) << "Reserving space for backing file isn't supported, "
- "may run out of disk later";
- }
+ if (auto err = mIncFs->reserveSpace(ifs->control, id, params.size)) {
+ if (err != -EOPNOTSUPP) {
+ LOG(ERROR) << "Failed to reserve space for a new file: " << err;
+ (void)mIncFs->unlink(ifs->control, normPath);
+ return err;
+ } else {
+ LOG(WARNING) << "Reserving space for backing file isn't supported, "
+ "may run out of disk later";
}
}
if (!data.empty()) {
@@ -1643,6 +1677,15 @@
}
}
+void IncrementalService::trimReservedSpaceV1(const IncFsMount& ifs) {
+ mIncFs->forEachFile(ifs.control, [this](auto&& control, auto&& fileId) {
+ if (mIncFs->isFileFullyLoaded(control, fileId) == incfs::LoadingState::Full) {
+ mIncFs->reserveSpace(control, fileId, -1);
+ }
+ return true;
+ });
+}
+
void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
DataLoaderStatusListener&& statusListener,
const StorageHealthCheckParams& healthCheckParams,
@@ -1662,6 +1705,22 @@
std::move(statusListener), healthCheckParams,
std::move(healthListener), path::join(ifs.root, constants().mount));
+ // pre-v2 IncFS doesn't do automatic reserved space trimming - need to run it manually
+ if (!(mIncFs->features() & incfs::Features::v2)) {
+ addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
+ if (!state.fullyLoaded) {
+ return true;
+ }
+
+ const auto ifs = getIfs(storageId);
+ if (!ifs) {
+ return false;
+ }
+ trimReservedSpaceV1(*ifs);
+ return false;
+ });
+ }
+
addIfsStateCallback(ifs.mountId, [this](StorageId storageId, IfsState state) -> bool {
if (!state.fullyLoaded || state.readLogsEnabled) {
return true;
@@ -2198,7 +2257,6 @@
affected.reserve(mMounts.size());
for (auto&& [id, ifs] : mMounts) {
std::unique_lock ll(ifs->lock);
-
if (ifs->mountId == id && ifs->dataLoaderStub &&
ifs->dataLoaderStub->params().packageName == packageName) {
affected.push_back(ifs);
@@ -2206,7 +2264,8 @@
}
}
for (auto&& ifs : affected) {
- applyStorageParamsLocked(*ifs, /*enableReadLogs=*/false);
+ std::unique_lock ll(ifs->lock);
+ disableReadLogsLocked(*ifs);
}
}
@@ -2235,7 +2294,7 @@
mIfsStateCallbacks[storageId].emplace_back(std::move(callback));
}
if (wasEmpty) {
- addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
[this]() { processIfsStateCallbacks(); });
}
}
@@ -2251,29 +2310,36 @@
}
IfsStateCallbacks::iterator it;
if (storageId == kInvalidStorageId) {
- // First entry, initialize the it.
+ // First entry, initialize the |it|.
it = mIfsStateCallbacks.begin();
} else {
- // Subsequent entries, update the storageId, and shift to the new one.
- it = mIfsStateCallbacks.find(storageId);
+ // Subsequent entries, update the |storageId|, and shift to the new one (not that
+ // it guarantees much about updated items, but at least the loop will finish).
+ it = mIfsStateCallbacks.lower_bound(storageId);
if (it == mIfsStateCallbacks.end()) {
- // Was removed while processing, too bad.
+ // Nothing else left, too bad.
break;
}
-
- auto& callbacks = it->second;
- if (callbacks.empty()) {
- std::swap(callbacks, local);
+ if (it->first != storageId) {
+ local.clear(); // Was removed during processing, forget the old callbacks.
} else {
- callbacks.insert(callbacks.end(), local.begin(), local.end());
- }
- if (callbacks.empty()) {
- it = mIfsStateCallbacks.erase(it);
- if (mIfsStateCallbacks.empty()) {
- return;
+ // Put the 'surviving' callbacks back into the map and advance the position.
+ auto& callbacks = it->second;
+ if (callbacks.empty()) {
+ std::swap(callbacks, local);
+ } else {
+ callbacks.insert(callbacks.end(), std::move_iterator(local.begin()),
+ std::move_iterator(local.end()));
+ local.clear();
}
- } else {
- ++it;
+ if (callbacks.empty()) {
+ it = mIfsStateCallbacks.erase(it);
+ if (mIfsStateCallbacks.empty()) {
+ return;
+ }
+ } else {
+ ++it;
+ }
}
}
@@ -2293,7 +2359,7 @@
processIfsStateCallbacks(storageId, local);
}
- addTimedJob(*mTimedQueue, kMaxStorageId, Constants::progressUpdateInterval,
+ addTimedJob(*mTimedQueue, kAllStoragesId, Constants::progressUpdateInterval,
[this]() { processIfsStateCallbacks(); });
}
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index a8f32de..fb6f56c 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -95,7 +95,8 @@
#pragma GCC diagnostic pop
static constexpr StorageId kInvalidStorageId = -1;
- static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max();
+ static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max() - 1;
+ static constexpr StorageId kAllStoragesId = kMaxStorageId + 1;
static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max();
@@ -116,6 +117,7 @@
enum StorageFlags {
ReadLogsAllowed = 1 << 0,
ReadLogsEnabled = 1 << 1,
+ ReadLogsRequested = 1 << 2,
};
struct LoadingProgress {
@@ -365,6 +367,9 @@
void setReadLogsEnabled(bool value);
int32_t readLogsEnabled() const { return (flags & StorageFlags::ReadLogsEnabled); }
+ void setReadLogsRequested(bool value);
+ int32_t readLogsRequested() const { return (flags & StorageFlags::ReadLogsRequested); }
+
static void cleanupFilesystem(std::string_view root);
};
@@ -447,6 +452,8 @@
StorageLoadingProgressListener&& progressListener);
long getMillsSinceOldestPendingRead(StorageId storage);
+ void trimReservedSpaceV1(const IncFsMount& ifs);
+
private:
const std::unique_ptr<VoldServiceWrapper> mVold;
const std::unique_ptr<DataLoaderManagerWrapper> mDataLoaderManager;
@@ -468,7 +475,7 @@
std::mutex mCallbacksLock;
std::unordered_map<std::string, sp<AppOpsListener>> mCallbackRegistered;
- using IfsStateCallbacks = std::unordered_map<StorageId, std::vector<IfsStateCallback>>;
+ using IfsStateCallbacks = std::map<StorageId, std::vector<IfsStateCallback>>;
std::mutex mIfsStateCallbacksLock;
IfsStateCallbacks mIfsStateCallbacks;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 3465499..8e416f3 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -212,6 +212,9 @@
std::string_view path) const final {
return incfs::isFullyLoaded(control, path);
}
+ incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const final {
+ return incfs::isFullyLoaded(control, id);
+ }
incfs::LoadingState isEverythingFullyLoaded(const Control& control) const final {
return incfs::isEverythingFullyLoaded(control);
}
@@ -227,9 +230,8 @@
ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
}
- ErrorCode reserveSpace(const Control& control, std::string_view path,
- IncFsSize size) const final {
- return incfs::reserveSpace(control, path, size);
+ ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const final {
+ return incfs::reserveSpace(control, id, size);
}
WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
@@ -238,19 +240,26 @@
ErrorCode setUidReadTimeouts(const Control& control,
const std::vector<android::os::incremental::PerUidReadTimeouts>&
perUidReadTimeouts) const final {
- std::vector<incfs::UidReadTimeouts> timeouts;
- timeouts.resize(perUidReadTimeouts.size());
+ std::vector<incfs::UidReadTimeouts> timeouts(perUidReadTimeouts.size());
for (int i = 0, size = perUidReadTimeouts.size(); i < size; ++i) {
- auto&& timeout = timeouts[i];
+ auto& timeout = timeouts[i];
const auto& perUidTimeout = perUidReadTimeouts[i];
timeout.uid = perUidTimeout.uid;
timeout.minTimeUs = perUidTimeout.minTimeUs;
timeout.minPendingTimeUs = perUidTimeout.minPendingTimeUs;
timeout.maxPendingTimeUs = perUidTimeout.maxPendingTimeUs;
}
-
return incfs::setUidReadTimeouts(control, timeouts);
}
+ ErrorCode forEachFile(const Control& control, FileCallback cb) const final {
+ return incfs::forEachFile(control,
+ [&](auto& control, FileId id) { return cb(control, id); });
+ }
+ ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const final {
+ return incfs::forEachIncompleteFile(control, [&](auto& control, FileId id) {
+ return cb(control, id);
+ });
+ }
};
static JNIEnv* getOrAttachJniEnv(JavaVM* jvm);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index a787db5..d4cdcbe 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -84,6 +84,8 @@
void(std::string_view root, std::string_view backingDir,
std::span<std::pair<std::string_view, std::string_view>> binds)>;
+ using FileCallback = android::base::function_ref<bool(const Control& control, FileId fileId)>;
+
static std::string toString(FileId fileId);
virtual ~IncFsWrapper() = default;
@@ -105,14 +107,14 @@
const Control& control, std::string_view path) const = 0;
virtual incfs::LoadingState isFileFullyLoaded(const Control& control,
std::string_view path) const = 0;
+ virtual incfs::LoadingState isFileFullyLoaded(const Control& control, FileId id) const = 0;
virtual incfs::LoadingState isEverythingFullyLoaded(const Control& control) const = 0;
virtual ErrorCode link(const Control& control, std::string_view from,
std::string_view to) const = 0;
virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0;
virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
- virtual ErrorCode reserveSpace(const Control& control, std::string_view path,
- IncFsSize size) const = 0;
+ virtual ErrorCode reserveSpace(const Control& control, FileId id, IncFsSize size) const = 0;
virtual WaitResult waitForPendingReads(
const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
@@ -120,6 +122,8 @@
const Control& control,
const std::vector<::android::os::incremental::PerUidReadTimeouts>& perUidReadTimeouts)
const = 0;
+ virtual ErrorCode forEachFile(const Control& control, FileCallback cb) const = 0;
+ virtual ErrorCode forEachIncompleteFile(const Control& control, FileCallback cb) const = 0;
};
class AppOpsManagerWrapper {
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
index 338659d..bf4e9616 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,19 +44,20 @@
PathCharsLess());
}
-static void preparePathComponent(std::string_view& path, bool trimFront) {
- if (trimFront) {
- while (!path.empty() && path.front() == '/') {
- path.remove_prefix(1);
- }
+static void preparePathComponent(std::string_view& path, bool trimAll) {
+ // need to check for double front slash as a single one has a separate meaning in front
+ while (!path.empty() && path.front() == '/' &&
+ (trimAll || (path.size() > 1 && path[1] == '/'))) {
+ path.remove_prefix(1);
}
- while (!path.empty() && path.back() == '/') {
+ // for the back we don't care about double-vs-single slash difference
+ while (path.size() > !trimAll && path.back() == '/') {
path.remove_suffix(1);
}
}
void details::append_next_path(std::string& target, std::string_view path) {
- preparePathComponent(path, true);
+ preparePathComponent(path, !target.empty());
if (path.empty()) {
return;
}
diff --git a/services/incremental/path.h b/services/incremental/path.h
index 3e5fd21..e12e1d0 100644
--- a/services/incremental/path.h
+++ b/services/incremental/path.h
@@ -89,15 +89,25 @@
bool startsWith(std::string_view path, std::string_view prefix);
template <class... Paths>
-std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
- std::string result;
+std::string join(std::string&& first, std::string_view second, Paths&&... paths) {
+ std::string& result = first;
{
using std::size;
result.reserve(first.size() + second.size() + 1 + (sizeof...(paths) + ... + size(paths)));
}
- result.assign(first);
- (details::append_next_path(result, second), ..., details::append_next_path(result, paths));
- return result;
+ (details::append_next_path(result, second), ...,
+ details::append_next_path(result, std::forward<Paths>(paths)));
+ return std::move(result);
+}
+
+template <class... Paths>
+std::string join(std::string_view first, std::string_view second, Paths&&... paths) {
+ return path::join(std::string(), first, second, std::forward<Paths>(paths)...);
+}
+
+template <class... Paths>
+std::string join(const char* first, std::string_view second, Paths&&... paths) {
+ return path::join(std::string_view(first), second, std::forward<Paths>(paths)...);
}
} // namespace android::incremental::path
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index e6d4872..ddb7784 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -379,6 +379,7 @@
std::string_view path));
MOCK_CONST_METHOD2(isFileFullyLoaded,
incfs::LoadingState(const Control& control, std::string_view path));
+ MOCK_CONST_METHOD2(isFileFullyLoaded, incfs::LoadingState(const Control& control, FileId id));
MOCK_CONST_METHOD1(isEverythingFullyLoaded, incfs::LoadingState(const Control& control));
MOCK_CONST_METHOD3(link,
ErrorCode(const Control& control, std::string_view from,
@@ -386,14 +387,15 @@
MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id));
MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
- MOCK_CONST_METHOD3(reserveSpace,
- ErrorCode(const Control& control, std::string_view path, IncFsSize size));
+ MOCK_CONST_METHOD3(reserveSpace, ErrorCode(const Control& control, FileId id, IncFsSize size));
MOCK_CONST_METHOD3(waitForPendingReads,
WaitResult(const Control& control, std::chrono::milliseconds timeout,
std::vector<incfs::ReadInfo>* pendingReadsBuffer));
MOCK_CONST_METHOD2(setUidReadTimeouts,
ErrorCode(const Control& control,
const std::vector<PerUidReadTimeouts>& perUidReadTimeouts));
+ MOCK_CONST_METHOD2(forEachFile, ErrorCode(const Control& control, FileCallback cb));
+ MOCK_CONST_METHOD2(forEachIncompleteFile, ErrorCode(const Control& control, FileCallback cb));
MockIncFs() {
ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return());
@@ -1594,7 +1596,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState::MissingBlocks));
ASSERT_GT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1605,7 +1607,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState(-1)));
ASSERT_LT((int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"), 0);
@@ -1616,7 +1618,7 @@
int storageId =
mIncrementalService->createStorage(tempDir.path, mDataLoaderParcel,
IncrementalService::CreateOptions::CreateNew);
- EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, _))
+ EXPECT_CALL(*mIncFs, isFileFullyLoaded(_, An<std::string_view>()))
.Times(1)
.WillOnce(Return(incfs::LoadingState::Full));
ASSERT_EQ(0, (int)mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
@@ -1735,10 +1737,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// IfsState callback present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
auto callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Not loaded yet.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1751,10 +1753,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_)).WillOnce(Return(incfs::LoadingState::Full));
@@ -1797,10 +1799,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// IfsState callback present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
auto callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Not loaded yet.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1813,10 +1815,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded.
EXPECT_CALL(*mIncFs, isEverythingFullyLoaded(_))
@@ -1832,10 +1834,10 @@
ASSERT_EQ(mDataLoader->status(), IDataLoaderStatusListener::DATA_LOADER_STARTED);
// Still present.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_EQ(mTimedQueue->mAfter, stateUpdateInterval);
callback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Disable readlogs and expect the unbind.
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
@@ -2007,10 +2009,10 @@
{
// Timed callback present -> 0 progress.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Call it again.
timedCallback();
@@ -2018,10 +2020,10 @@
{
// Still present -> some progress.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Fully loaded but readlogs collection enabled.
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
@@ -2032,10 +2034,10 @@
{
// Still present -> fully loaded + readlogs.
- ASSERT_EQ(IncrementalService::kMaxStorageId, mTimedQueue->mId);
+ ASSERT_EQ(IncrementalService::kAllStoragesId, mTimedQueue->mId);
ASSERT_GE(mTimedQueue->mAfter, std::chrono::seconds(1));
const auto timedCallback = mTimedQueue->mWhat;
- mTimedQueue->clearJob(IncrementalService::kMaxStorageId);
+ mTimedQueue->clearJob(IncrementalService::kAllStoragesId);
// Now disable readlogs.
ASSERT_GE(mDataLoader->setStorageParams(false), 0);
diff --git a/services/incremental/test/path_test.cpp b/services/incremental/test/path_test.cpp
index cbe479e1..4a8ae5b 100644
--- a/services/incremental/test/path_test.cpp
+++ b/services/incremental/test/path_test.cpp
@@ -40,4 +40,24 @@
EXPECT_TRUE(!PathLess()("/a/b", "/a"));
}
+TEST(Path, Join) {
+ EXPECT_STREQ("", path::join("", "").c_str());
+
+ EXPECT_STREQ("/", path::join("", "/").c_str());
+ EXPECT_STREQ("/", path::join("/", "").c_str());
+ EXPECT_STREQ("/", path::join("/", "/").c_str());
+ EXPECT_STREQ("/", path::join("/"s, "/").c_str());
+ EXPECT_STREQ("/", path::join("/"sv, "/").c_str());
+ EXPECT_STREQ("/", path::join("/", "/", "/", "/", "/", "/", "/", "/", "/", "/").c_str());
+
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b/"s, "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b/", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b/", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "a/b", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("", "", "/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join(""s, "", "/", "//a/b//", "c", "d").c_str());
+ EXPECT_STREQ("/a/b/c/d", path::join("/a/b", "", "", "/", "", "/", "/", "/c", "d").c_str());
+}
+
} // namespace android::incremental::path
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index a262939..29aedce 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -295,10 +295,30 @@
return;
}
- try {
- mIProfcollect.report();
- } catch (RemoteException e) {
- Log.e(LOG_TAG, e.getMessage());
- }
+ final boolean uploadReport =
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PROFCOLLECT_NATIVE_BOOT,
+ "upload_report", false);
+
+ new Thread(() -> {
+ try {
+ String reportPath = mIProfcollect.report();
+ if (!uploadReport) {
+ return;
+ }
+ Intent uploadIntent =
+ new Intent("com.google.android.apps.betterbug.intent.action.UPLOAD_PROFILE")
+ .setPackage("com.google.android.apps.internal.betterbug")
+ .putExtra("EXTRA_DESTINATION", "PROFCOLLECT")
+ .putExtra("EXTRA_PACKAGE_NAME", getContext().getPackageName())
+ .putExtra("EXTRA_PROFILE_PATH", reportPath)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Context context = getContext();
+ if (context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0) != null) {
+ context.sendBroadcast(uploadIntent);
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, e.getMessage());
+ }
+ }).start();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
index 22b2f7e..d220444 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationExitInfoTest.java
@@ -1015,7 +1015,7 @@
assertNotNull(info);
if (timestamp != null) {
- final long tolerance = 1000; // ms
+ final long tolerance = 10000; // ms
assertTrue(timestamp - tolerance <= info.getTimestamp());
assertTrue(timestamp + tolerance >= info.getTimestamp());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 17c6b6f..db0c3ae 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -36,6 +36,7 @@
import android.os.UserHandle
import android.os.UserManager
import android.os.incremental.IncrementalManager
+import android.provider.DeviceConfig
import android.util.ArrayMap
import android.util.DisplayMetrics
import android.util.EventLog
@@ -131,6 +132,7 @@
.mockStatic(LockGuard::class.java)
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
+ .mockStatic(DeviceConfig::class.java)
.apply(withSession)
session = apply.startMocking()
whenever(mocks.settings.insertPackageSettingLPw(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
index a0e208f..46487ea2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceHibernationTests.kt
@@ -17,8 +17,13 @@
package com.android.server.pm
import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
import com.android.server.apphibernation.AppHibernationManagerInternal
+import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.testutils.whenever
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -33,7 +38,10 @@
companion object {
val TEST_PACKAGE_NAME = "test.package"
+ val TEST_PACKAGE_2_NAME = "test.package2"
val TEST_USER_ID = 0
+
+ val KEY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"
}
@Rule
@@ -47,6 +55,8 @@
@Throws(Exception::class)
fun setup() {
MockitoAnnotations.initMocks(this)
+ wheneverStatic { DeviceConfig.getBoolean(
+ NAMESPACE_APP_HIBERNATION, KEY_APP_HIBERNATION_ENABLED, false) }.thenReturn(true)
rule.system().stageNominalSystemState()
whenever(rule.mocks().injector.getLocalService(AppHibernationManagerInternal::class.java))
.thenReturn(appHibernationManager)
@@ -68,6 +78,28 @@
verify(appHibernationManager).setHibernatingGlobally(TEST_PACKAGE_NAME, false)
}
+ @Test
+ fun testGetOptimizablePackages_ExcludesGloballyHibernatingPackages() {
+ rule.system().stageScanExistingPackage(
+ TEST_PACKAGE_NAME,
+ 1L,
+ rule.system().dataAppDirectory,
+ withPackage = { it.apply { isHasCode = true } })
+ rule.system().stageScanExistingPackage(
+ TEST_PACKAGE_2_NAME,
+ 1L,
+ rule.system().dataAppDirectory,
+ withPackage = { it.apply { isHasCode = true } })
+ val pm = createPackageManagerService()
+ rule.system().validateFinalState()
+ whenever(appHibernationManager.isHibernatingGlobally(TEST_PACKAGE_2_NAME)).thenReturn(true)
+
+ val optimizablePkgs = pm.optimizablePackages
+
+ assertTrue(optimizablePkgs.contains(TEST_PACKAGE_NAME))
+ assertFalse(optimizablePkgs.contains(TEST_PACKAGE_2_NAME))
+ }
+
private fun createPackageManagerService(): PackageManagerService {
return PackageManagerService(rule.mocks().injector,
false /*coreOnly*/,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
new file mode 100644
index 0000000..7a6110b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.pm
+
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.SparseArray
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.android.server.testutils.whenever
+import com.android.server.utils.WatchedArrayMap
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Captor
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class SuspendPackagesBroadcastTest {
+
+ companion object {
+ const val TEST_PACKAGE_1 = "com.android.test.package1"
+ const val TEST_PACKAGE_2 = "com.android.test.package2"
+ const val TEST_USER_ID = 0
+ }
+
+ lateinit var pms: PackageManagerService
+ lateinit var packageSetting1: PackageSetting
+ lateinit var packageSetting2: PackageSetting
+ lateinit var packagesToSuspend: Array<String>
+ lateinit var uidsToSuspend: IntArray
+
+ @Captor
+ lateinit var bundleCaptor: ArgumentCaptor<Bundle>
+
+ @Rule
+ @JvmField
+ val rule = MockSystemRule()
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ rule.system().stageNominalSystemState()
+ pms = spy(createPackageManagerService(TEST_PACKAGE_1, TEST_PACKAGE_2))
+ packageSetting1 = pms.getPackageSetting(TEST_PACKAGE_1)!!
+ packageSetting2 = pms.getPackageSetting(TEST_PACKAGE_2)!!
+ packagesToSuspend = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ uidsToSuspend = intArrayOf(packageSetting1.appId, packageSetting2.appId)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withSameVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+ var changedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids).asList().containsExactly(
+ packageSetting1.appId, packageSetting2.appId)
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withDifferentVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
+
+ bundleCaptor.allValues.forEach {
+ var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages?.size).isEqualTo(1)
+ assertThat(changedUids?.size).isEqualTo(1)
+ assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+ }
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendedForUser_withNullVisibilityAllowList() {
+ mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
+ mockAllowList(packageSetting2, null)
+
+ pms.sendPackagesSuspendedForUser(
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ bundleCaptor.allValues.forEach {
+ var changedPackages = it.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var changedUids = it.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(changedPackages?.size).isEqualTo(1)
+ assertThat(changedUids?.size).isEqualTo(1)
+ assertThat(changedPackages?.get(0)).isAnyOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(changedUids?.get(0)).isAnyOf(packageSetting1.appId, packageSetting2.appId)
+ }
+ }
+
+ private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
+ this.put(TEST_USER_ID, uids)
+ }
+
+ private fun mockAllowList(pkgSetting: PackageSetting, list: SparseArray<IntArray>?) {
+ whenever(rule.mocks().injector.appsFilter.getVisibilityAllowList(eq(pkgSetting),
+ any(IntArray::class.java), any() as WatchedArrayMap<String, PackageSetting>))
+ .thenReturn(list)
+ }
+
+ private fun createPackageManagerService(vararg stageExistingPackages: String):
+ PackageManagerService {
+ stageExistingPackages.forEach {
+ rule.system().stageScanExistingPackage(it, 1L,
+ rule.system().dataAppDirectory)
+ }
+ var pms = PackageManagerService(rule.mocks().injector,
+ false /*coreOnly*/,
+ false /*factoryTest*/,
+ MockSystem.DEFAULT_VERSION_INFO.fingerprint,
+ false /*isEngBuild*/,
+ false /*isUserDebugBuild*/,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ Build.VERSION.INCREMENTAL)
+ rule.system().validateFinalState()
+ return pms
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 9ffb5017..5c8a7d2 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -80,6 +80,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
@@ -123,6 +124,7 @@
private static final long HANDLER_WAIT_TIME_MS = 100;
private UserController mUserController;
+ private LockPatternUtils mLockPatternUtils;
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
@@ -161,6 +163,13 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).taskSupervisorRemoveUser(anyInt());
+
+ // Make it appear that calling unlockUserKey() is needed.
+ doReturn(true).when(mInjector).isFileEncryptedNativeOnly();
+ mLockPatternUtils = mock(LockPatternUtils.class);
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false);
+ doReturn(mLockPatternUtils).when(mInjector).getLockPatternUtils();
+
// All UserController params are set to default.
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
@@ -552,6 +561,20 @@
/* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
}
+ /**
+ * Test that if a user has a lock screen credential set, then UserController
+ * doesn't bother trying to unlock their storage key without a credential
+ * token, as it will never work.
+ */
+ @Test
+ public void testSecureUserUnlockNotAttempted() throws Exception {
+ when(mLockPatternUtils.isSecure(eq(TEST_USER_ID1))).thenReturn(true);
+ setUpUser(TEST_USER_ID1, 0);
+ mUserController.startUser(TEST_USER_ID1, /* foreground= */ false);
+ verify(mInjector.mStorageManagerMock, times(0))
+ .unlockUserKey(eq(TEST_USER_ID1), anyInt(), any(), any());
+ }
+
@Test
public void testStartProfile_fullUserFails() {
setUpUser(TEST_USER_ID1, 0);
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
index 2f0d71a..0dbf3fe 100644
--- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.IActivityManager;
@@ -36,6 +37,7 @@
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.net.Uri;
@@ -53,7 +55,6 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
@@ -82,6 +83,8 @@
@Mock
private IPackageManager mIPackageManager;
@Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
private IActivityManager mIActivityManager;
@Mock
private UserManager mUserManager;
@@ -255,6 +258,11 @@
}
@Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
public UserManager getUserManager() {
return mUserManager;
}
@@ -267,12 +275,12 @@
@Override
public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() {
- return Mockito.mock(HibernationStateDiskStore.class);
+ return mock(HibernationStateDiskStore.class);
}
@Override
public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) {
- return Mockito.mock(HibernationStateDiskStore.class);
+ return mock(HibernationStateDiskStore.class);
}
}
}
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/biometrics/sensors/UserAwareBiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
index 6cdac1a..557c14a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/UserAwareBiometricSchedulerTest.java
@@ -81,7 +81,7 @@
@NonNull
@Override
- public StartUserClient<?> getStartUserClient(int newUserId) {
+ public StartUserClient<?, ?> getStartUserClient(int newUserId) {
return new TestStartUserClient(mContext, Object::new, mToken, newUserId,
TEST_SENSOR_ID, mUserStartedCallback);
}
@@ -157,12 +157,12 @@
}
}
- private class TestUserStartedCallback implements StartUserClient.UserStartedCallback {
+ private class TestUserStartedCallback implements StartUserClient.UserStartedCallback<Object> {
int numInvocations;
@Override
- public void onUserStarted(int newUserId) {
+ public void onUserStarted(int newUserId, Object newObject) {
numInvocations++;
mCurrentUserId = newUserId;
}
@@ -183,8 +183,7 @@
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
- mUserStoppedCallback.onUserStopped();
- callback.onClientFinished(this, true /* success */);
+ onUserStopped();
}
@Override
@@ -193,10 +192,10 @@
}
}
- private static class TestStartUserClient extends StartUserClient<Object> {
+ private static class TestStartUserClient extends StartUserClient<Object, Object> {
public TestStartUserClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @Nullable IBinder token, int userId,
- int sensorId, @NonNull UserStartedCallback callback) {
+ int sensorId, @NonNull UserStartedCallback<Object> callback) {
super(context, lazyDaemon, token, userId, sensorId, callback);
}
@@ -208,7 +207,7 @@
@Override
public void start(@NonNull Callback callback) {
super.start(callback);
- mUserStartedCallback.onUserStarted(getTargetUserId());
+ mUserStartedCallback.onUserStarted(getTargetUserId(), new Object());
callback.onClientFinished(this, true /* success */);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index 04a7122..0cd6d86 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -24,10 +24,12 @@
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -56,7 +58,7 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FaceProvider mFaceProvider;
+ private TestableFaceProvider mFaceProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -80,7 +82,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFaceProvider = new FaceProvider(mContext, mSensorProps, TAG,
+ mFaceProvider = new TestableFaceProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher);
}
@@ -123,4 +125,19 @@
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
+
+ private static class TestableFaceProvider extends FaceProvider {
+ public TestableFaceProvider(@NonNull Context context,
+ @NonNull SensorProps[] props,
+ @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher) {
+ super(context, props, halInstanceName, lockoutResetDispatcher);
+ }
+
+ @Override
+ synchronized IFace getHalInstance() {
+ return mock(IFace.class);
+ }
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index d149880..94cc666 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -24,10 +24,12 @@
import android.content.Context;
import android.hardware.biometrics.common.CommonProps;
+import android.hardware.biometrics.fingerprint.IFingerprint;
import android.hardware.biometrics.fingerprint.SensorProps;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -59,7 +61,7 @@
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
- private FingerprintProvider mFingerprintProvider;
+ private TestableFingerprintProvider mFingerprintProvider;
private static void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -83,7 +85,7 @@
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new FingerprintProvider(mContext, mSensorProps, TAG,
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@@ -126,4 +128,20 @@
assertEquals(0, scheduler.getCurrentPendingCount());
}
}
+
+ private static class TestableFingerprintProvider extends FingerprintProvider {
+ public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull SensorProps[] props,
+ @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
+ super(context, props, halInstanceName, lockoutResetDispatcher,
+ gestureAvailabilityDispatcher);
+ }
+
+ @Override
+ synchronized IFingerprint getHalInstance() {
+ return mock(IFingerprint.class);
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index dbb415c..e7ffea0 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -54,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
@Presubmit
@@ -117,10 +118,13 @@
}
}
+ private static final long CURRENT_TIME = 1234567890L;
+
private File mCacheDir;
private File mUpdatableFontFilesDir;
private File mConfigFile;
private List<File> mPreinstalledFontDirs;
+ private Supplier<Long> mCurrentTimeSupplier = () -> CURRENT_TIME;
@SuppressWarnings("ResultOfMethodCallIgnored")
@Before
@@ -147,7 +151,7 @@
@Test
public void construct() throws Exception {
- long expectedModifiedDate = 1234567890;
+ long expectedModifiedDate = CURRENT_TIME / 2;
FakeFontFileParser parser = new FakeFontFileParser();
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
PersistentSystemFontConfig.Config config = new PersistentSystemFontConfig.Config();
@@ -155,7 +159,7 @@
writeConfig(config, mConfigFile);
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
.isEqualTo(expectedModifiedDate);
@@ -168,6 +172,9 @@
+ " <font>foo.ttf</font>"
+ " <font>bar.ttf</font>"
+ "</family>")));
+ // Verifies that getLastModifiedTimeMillis() returns the value of currentTimeMillis.
+ assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
+ .isEqualTo(CURRENT_TIME);
// Four font dirs are created.
assertThat(mUpdatableFontFilesDir.list()).hasLength(4);
assertThat(dirForPreparation.getSystemFontConfig().getLastModifiedTimeMillis())
@@ -175,7 +182,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
assertThat(parser.getRevision(dir.getFontFileMap().get("foo.ttf"))).isEqualTo(3);
@@ -199,7 +206,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
@@ -211,7 +218,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -229,7 +236,7 @@
dirForPreparation.getFontFileMap().get("foo.ttf").getAbsolutePath());
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
@@ -243,7 +250,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -262,7 +269,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
// All font dirs (including dir for "bar.ttf") should be deleted.
@@ -276,7 +283,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -296,7 +303,7 @@
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(1), "bar.ttf"), "bar,2");
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// For foo.ttf, preinstalled font (revision 5) should be used.
assertThat(dir.getFontFileMap()).doesNotContainKey("foo.ttf");
@@ -317,7 +324,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- new File("/dev/null"));
+ new File("/dev/null"), mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getFontFileMap()).isEmpty();
assertThat(dir.getFontFamilyMap()).isEmpty();
@@ -329,7 +336,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dirForPreparation = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dirForPreparation.loadFontFileMap();
dirForPreparation.update(Arrays.asList(
newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE),
@@ -351,7 +358,7 @@
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// The state should be rolled back as a whole if one of the update requests fail.
assertThat(dir.getFontFileMap()).containsKey("foo.ttf");
@@ -369,7 +376,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE)));
@@ -387,7 +394,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,1", GOOD_SIGNATURE)));
@@ -406,7 +413,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("test.ttf,2", GOOD_SIGNATURE)));
@@ -428,7 +435,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE)));
@@ -445,7 +452,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Arrays.asList(
@@ -463,7 +470,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -485,7 +492,7 @@
FileUtils.stringToFile(new File(mPreinstalledFontDirs.get(0), "test.ttf"), "test.ttf,1");
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -517,7 +524,7 @@
try {
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- readonlyFile);
+ readonlyFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -551,7 +558,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -580,7 +587,7 @@
public long getRevision(File file) throws IOException {
return 0;
}
- }, fakeFsverityUtil, mConfigFile);
+ }, fakeFsverityUtil, mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -617,7 +624,7 @@
FakeFontFileParser parser = new FakeFontFileParser();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -637,7 +644,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Collections.singletonList(newFontUpdateRequest("foo.ttf,1", GOOD_SIGNATURE)));
@@ -660,7 +667,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
dir.update(Arrays.asList(
@@ -682,7 +689,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -703,7 +710,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
try {
@@ -723,7 +730,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
// We assume we have monospace.
assertNamedFamilyExists(dir.getSystemFontConfig(), "monospace");
@@ -755,7 +762,7 @@
FakeFsverityUtil fakeFsverityUtil = new FakeFsverityUtil();
UpdatableFontDir dir = new UpdatableFontDir(
mUpdatableFontFilesDir, mPreinstalledFontDirs, parser, fakeFsverityUtil,
- mConfigFile);
+ mConfigFile, mCurrentTimeSupplier);
dir.loadFontFileMap();
assertThat(dir.getSystemFontConfig().getFontFamilies()).isNotEmpty();
FontConfig.FontFamily firstFontFamily = dir.getSystemFontConfig().getFontFamilies().get(0);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
deleted file mode 100644
index 70718f7..0000000
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceBinderAPITest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.hdmi;
-
-import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertEquals;
-
-import android.content.Context;
-import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiPortInfo;
-import android.hardware.hdmi.IHdmiControlCallback;
-import android.os.Looper;
-import android.os.test.TestLooper;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Tests for {@link HdmiControlServiceBinderAPITest} class.
- */
-@SmallTest
-@RunWith(JUnit4.class)
-public class HdmiControlServiceBinderAPITest {
-
- private Context mContext;
-
- private class HdmiCecLocalDeviceMyDevice extends HdmiCecLocalDevice {
-
- private boolean mCanGoToStandby;
- private boolean mIsStandby;
- private boolean mIsDisabled;
-
- protected HdmiCecLocalDeviceMyDevice(HdmiControlService service, int deviceType) {
- super(service, deviceType);
- }
-
- @Override
- protected void onAddressAllocated(int logicalAddress, int reason) {
- }
-
- @Override
- protected int getPreferredAddress() {
- return 0;
- }
-
- @Override
- protected void setPreferredAddress(int addr) {
- }
-
- @Override
- protected boolean canGoToStandby() {
- return mCanGoToStandby;
- }
-
- @Override
- protected void disableDevice(
- boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
- mIsDisabled = true;
- originalCallback.onCleared(this);
- }
-
- @Override
- protected void onStandby(boolean initiatedByCec, int standbyAction) {
- mIsStandby = true;
- }
-
- protected boolean isStandby() {
- return mIsStandby;
- }
-
- protected boolean isDisabled() {
- return mIsDisabled;
- }
-
- protected void setCanGoToStandby(boolean canGoToStandby) {
- mCanGoToStandby = canGoToStandby;
- }
-
- @Override
- protected int getRcProfile() {
- return 0;
- }
-
- @Override
- protected List<Integer> getRcFeatures() {
- return Collections.emptyList();
- }
-
- @Override
- protected List<Integer> getDeviceFeatures() {
- return Collections.emptyList();
- }
- }
-
- private static final String TAG = "HdmiControlServiceBinderAPITest";
- private HdmiControlService mHdmiControlService;
- private HdmiCecController mHdmiCecController;
- private HdmiCecLocalDevicePlayback mPlaybackDevice;
- private FakeNativeWrapper mNativeWrapper;
- private Looper mMyLooper;
- private TestLooper mTestLooper = new TestLooper();
- private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
- private HdmiPortInfo[] mHdmiPortInfo;
- private int mResult;
- private int mPowerStatus;
-
- @Before
- public void SetUp() {
- mContext = InstrumentationRegistry.getTargetContext();
- // Some tests expect no logical addresses being allocated at the beginning of the test.
- setHdmiControlEnabled(false);
-
- HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContext);
-
- mHdmiControlService =
- new HdmiControlService(mContext) {
- @Override
- void sendCecCommand(HdmiCecMessage command) {
- switch (command.getOpcode()) {
- case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS:
- HdmiCecMessage message =
- HdmiCecMessageBuilder.buildReportPowerStatus(
- Constants.ADDR_TV,
- Constants.ADDR_PLAYBACK_1,
- HdmiControlManager.POWER_STATUS_ON);
- handleCecCommand(message);
- break;
- default:
- return;
- }
- }
-
- @Override
- boolean isPowerStandby() {
- return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
- }
-
- @Override
- protected HdmiCecConfig getHdmiCecConfig() {
- return hdmiCecConfig;
- }
- };
- mMyLooper = mTestLooper.getLooper();
-
- mPlaybackDevice = new HdmiCecLocalDevicePlayback(mHdmiControlService) {
- @Override
- protected void wakeUpIfActiveSource() {}
-
- @Override
- protected void setPreferredAddress(int addr) {}
-
- @Override
- protected int getPreferredAddress() {
- return Constants.ADDR_PLAYBACK_1;
- }
- };
- mPlaybackDevice.init();
-
- mHdmiControlService.setIoLooper(mMyLooper);
-
- mNativeWrapper = new FakeNativeWrapper();
- mHdmiCecController = HdmiCecController.createWithNativeWrapper(
- mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
- mHdmiControlService.setCecController(mHdmiCecController);
- mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
- mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService));
-
- mLocalDevices.add(mPlaybackDevice);
- mHdmiPortInfo = new HdmiPortInfo[1];
- mHdmiPortInfo[0] =
- new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
- mNativeWrapper.setPortInfo(mHdmiPortInfo);
- mHdmiControlService.initService();
- mResult = -1;
- mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
-
- mTestLooper.dispatchAll();
- }
-
- @Test
- public void oneTouchPlay_addressNotAllocated() {
- assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
- mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
- @Override
- public void onComplete(int result) {
- mResult = result;
- }
- });
- assertEquals(mResult, -1);
- assertThat(mPlaybackDevice.isActiveSource()).isFalse();
-
- setHdmiControlEnabled(true);
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
- assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.isActiveSource()).isTrue();
- }
-
- @Test
- public void oneTouchPlay_addressAllocated() {
- setHdmiControlEnabled(true);
-
- mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
- mTestLooper.dispatchAll();
- assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
- mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
- @Override
- public void onComplete(int result) {
- mResult = result;
- }
- });
- assertEquals(mResult, HdmiControlManager.RESULT_SUCCESS);
- assertThat(mPlaybackDevice.isActiveSource()).isTrue();
- }
-
- private void setHdmiControlEnabled(boolean enabled) {
- int value = enabled ? 1 : 0;
- Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.HDMI_CONTROL_ENABLED,
- value);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index c61635c..d74bff2 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -37,13 +37,13 @@
import android.os.Looper;
import android.os.PowerManager;
import android.os.test.TestLooper;
+import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.server.hdmi.HdmiCecFeatureAction.ActionTimer;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -79,12 +79,18 @@
@Mock
private IThermalService mIThermalServiceMock;
- @Before
- public void setUp() throws Exception {
+ /**
+ * Manually called before tests, because some tests require HDMI control to be disabled.
+ * @param hdmiControlEnabled whether to enable the global setting hdmi_control.
+ * @throws Exception
+ */
+ public void setUp(boolean hdmiControlEnabled) throws Exception {
MockitoAnnotations.initMocks(this);
mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+ setHdmiControlEnabled(hdmiControlEnabled);
+
PowerManager powerManager = new PowerManager(mContextSpy, mIPowerManagerMock,
mIThermalServiceMock, new Handler(mTestLooper.getLooper()));
when(mContextSpy.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager);
@@ -146,7 +152,9 @@
}
@Test
- public void succeedWithUnknownTvDevice() {
+ public void succeedWithUnknownTvDevice() throws Exception {
+ setUp(true);
+
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
playbackDevice.init();
@@ -185,7 +193,9 @@
}
@Test
- public void succeedAfterGettingPowerStatusOn_Cec14b() {
+ public void succeedAfterGettingPowerStatusOn_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -225,7 +235,9 @@
}
@Test
- public void succeedAfterGettingTransientPowerStatus_Cec14b() {
+ public void succeedAfterGettingTransientPowerStatus_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -275,7 +287,9 @@
}
@Test
- public void timeOut_Cec14b() {
+ public void timeOut_Cec14b() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -316,7 +330,9 @@
}
@Test
- public void succeedIfPowerStatusOn_Cec20() {
+ public void succeedIfPowerStatusOn_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -348,7 +364,9 @@
}
@Test
- public void succeedIfPowerStatusUnknown_Cec20() {
+ public void succeedIfPowerStatusUnknown_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -390,7 +408,9 @@
}
@Test
- public void succeedIfPowerStatusStandby_Cec20() {
+ public void succeedIfPowerStatusStandby_Cec20() throws Exception {
+ setUp(true);
+
mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
mHdmiControlService);
@@ -431,6 +451,73 @@
assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
+ @Test
+ public void succeedWithAddressNotAllocated_Cec14b() throws Exception {
+ setUp(false);
+
+ assertThat(mHdmiControlService.isAddressAllocated()).isFalse();
+
+ HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ playbackDevice.init();
+ mLocalDevices.add(playbackDevice);
+
+ TestCallback callback = new TestCallback();
+
+ mHdmiControlService.oneTouchPlay(callback);
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.hasResult()).isFalse();
+ assertThat(playbackDevice.isActiveSource()).isFalse();
+
+ setHdmiControlEnabled(true);
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ playbackDevice.mAddress,
+ HdmiControlManager.POWER_STATUS_ON
+ );
+ mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ assertThat(playbackDevice.isActiveSource()).isTrue();
+ }
+
+ @Test
+ public void succeedWithAddressAllocated_Cec14b() throws Exception {
+ setUp(true);
+
+ HdmiCecLocalDevicePlayback playbackDevice = new HdmiCecLocalDevicePlayback(
+ mHdmiControlService);
+ playbackDevice.init();
+ mLocalDevices.add(playbackDevice);
+
+ mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+ mTestLooper.dispatchAll();
+ assertThat(mHdmiControlService.isAddressAllocated()).isTrue();
+
+ TestCallback callback = new TestCallback();
+ mHdmiControlService.oneTouchPlay(callback);
+
+ HdmiCecMessage reportPowerStatusMessage = HdmiCecMessageBuilder.buildReportPowerStatus(
+ Constants.ADDR_TV,
+ playbackDevice.mAddress,
+ HdmiControlManager.POWER_STATUS_ON
+ );
+ mNativeWrapper.onCecMessage(reportPowerStatusMessage);
+
+ mTestLooper.dispatchAll();
+
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+ assertThat(playbackDevice.isActiveSource()).isTrue();
+ }
+
private static class TestActionTimer implements ActionTimer {
private int mState;
@@ -456,9 +543,19 @@
mCallbackResult.add(result);
}
+ private boolean hasResult() {
+ return mCallbackResult.size() != 0;
+ }
+
private int getResult() {
assertThat(mCallbackResult.size()).isEqualTo(1);
return mCallbackResult.get(0);
}
}
+
+ private void setHdmiControlEnabled(boolean enabled) {
+ int value = enabled ? 1 : 0;
+ Settings.Global.putInt(mContextSpy.getContentResolver(),
+ Settings.Global.HDMI_CONTROL_ENABLED, value);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index fb01ff6..100d3ea 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1773,57 +1773,75 @@
true);
}
- /**
- * Test that when StatsProvider triggers limit reached, new limit will be calculated and
- * re-armed.
- */
- @Test
- public void testStatsProviderLimitReached() throws Exception {
- final int CYCLE_DAY = 15;
-
- final NetworkStats stats = new NetworkStats(0L, 1);
+ private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- 2999, 1, 2000, 1, 0);
+ rxBytes, 1, txBytes, 1, 0);
when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
.thenReturn(stats.getTotalBytes());
when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
.thenReturn(stats);
+ }
+
+ private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException {
+ final NetworkPolicyManagerInternal npmi = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+ npmi.onStatsProviderWarningOrLimitReached("TEST");
+ // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED.
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).forceUpdate();
+ // Wait for processing of MSG_*_INTERFACE_QUOTAS.
+ postMsgAndWaitForCompletion();
+ }
+
+ /**
+ * Test that when StatsProvider triggers warning and limit reached, new quotas will be
+ * calculated and re-armed.
+ */
+ @Test
+ public void testStatsProviderWarningAndLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ increaseMockedTotalBytes(stats, 2999, 2000);
// Get active mobile network in place
expectMobileDefaults();
mService.updateNetworks();
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE);
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
+ Long.MAX_VALUE);
- // Set limit to 10KB.
+ // Set warning to 7KB and limit to 10KB.
setNetworkPolicies(new NetworkPolicy(
- sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
- true));
+ sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true));
postMsgAndWaitForCompletion();
- // Verifies that remaining quota is set to providers.
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L);
-
+ // Verifies that remaining quotas are set to providers.
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
reset(mStatsService);
- // Increase the usage.
- stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
- 1000, 1, 999, 1, 0);
- when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats.getTotalBytes());
- when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
- .thenReturn(stats);
+ // Increase the usage and simulates that limit reached fires earlier by provider,
+ // but actually the quota is not yet reached. Verifies that the limit reached leads to
+ // a force update and new quotas should be set.
+ increaseMockedTotalBytes(stats, 1000, 999);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
+ reset(mStatsService);
- // Simulates that limit reached fires earlier by provider, but actually the quota is not
- // yet reached.
- final NetworkPolicyManagerInternal npmi = LocalServices
- .getService(NetworkPolicyManagerInternal.class);
- npmi.onStatsProviderLimitReached("TEST");
+ // Increase the usage and simulate warning reached, the new warning should be unlimited
+ // since service will disable warning quota to stop lower layer from keep triggering
+ // warning reached event.
+ increaseMockedTotalBytes(stats, 1000L, 1000);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(
+ TEST_IFACE, Long.MAX_VALUE, 1002L);
+ reset(mStatsService);
- // Verifies that the limit reached leads to a force update and new limit should be set.
- postMsgAndWaitForCompletion();
- verify(mStatsService).forceUpdate();
- postMsgAndWaitForCompletion();
- verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L);
+ // Increase the usage that over the warning and limit, the new limit should set to 1 to
+ // block the network traffic.
+ increaseMockedTotalBytes(stats, 1000L, 1000);
+ triggerOnStatsProviderWarningOrLimitReached();
+ verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
+ reset(mStatsService);
}
/**
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/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index c6e35cf..e932905 100644
--- a/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -93,3 +93,24 @@
spyThrowOnUnmocked<T>(null, block)
inline fun <reified T : Any> nullable() = ArgumentMatchers.nullable(T::class.java)
+
+/**
+ * Returns Mockito.any() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
+
+/**
+ * Wrapper around [Mockito.any] for generic types.
+ */
+inline fun <reified T> any() = any(T::class.java)
+
+/**
+ * Returns Mockito.eq() as nullable type to avoid java.lang.IllegalStateException when
+ * null is returned.
+ *
+ * Generic T is nullable because implicitly bounded by Any?.
+ */
+fun <T> eq(obj: T): T = Mockito.eq<T>(obj)
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/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 748e5df..22a6445 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -7,6 +7,14 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsAssistTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
}
]
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index c9b0a3e..0d4c302 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -54,7 +54,6 @@
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionServiceInfo;
import android.system.OsConstants;
-import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.IWindowManager;
@@ -64,6 +63,7 @@
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.LocalServices;
+import com.android.server.wm.ActivityAssistInfo;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.ActivityTokens;
@@ -187,24 +187,23 @@
mSessionComponentName, mUser, mContext, this,
mInfo.getServiceInfo().applicationInfo.uid, mHandler);
}
- List<Pair<IBinder, Integer>> allVisibleActivities =
+ List<ActivityAssistInfo> allVisibleActivities =
LocalServices.getService(ActivityTaskManagerInternal.class)
.getTopVisibleActivities();
- List<Pair<IBinder, Integer>> visibleActivities = null;
+ List<ActivityAssistInfo> visibleActivities = null;
if (activityToken != null) {
visibleActivities = new ArrayList();
int activitiesCount = allVisibleActivities.size();
for (int i = 0; i < activitiesCount; i++) {
- if (allVisibleActivities.get(i).first == activityToken) {
- visibleActivities.add(
- new Pair<>(activityToken, allVisibleActivities.get(i).second));
+ ActivityAssistInfo info = allVisibleActivities.get(i);
+ if (info.getActivityToken() == activityToken) {
+ visibleActivities.add(info);
break;
}
}
} else {
- visibleActivities = LocalServices.getService(ActivityTaskManagerInternal.class)
- .getTopVisibleActivities();
+ visibleActivities = allVisibleActivities;
}
return mActiveSession.showLocked(args, flags, mDisabledShowContext, showCallback,
visibleActivities);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 428d342..cc021a9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -56,7 +56,6 @@
import android.service.voice.IVoiceInteractionSessionService;
import android.service.voice.VoiceInteractionService;
import android.service.voice.VoiceInteractionSession;
-import android.util.Pair;
import android.util.Slog;
import android.view.IWindowManager;
@@ -68,6 +67,7 @@
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
+import com.android.server.wm.ActivityAssistInfo;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -102,6 +102,7 @@
IVoiceInteractionSession mSession;
IVoiceInteractor mInteractor;
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
+ private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
IVoiceInteractionSessionShowCallback mShowCallback =
@@ -192,7 +193,7 @@
public boolean showLocked(Bundle args, int flags, int disabledContext,
IVoiceInteractionSessionShowCallback showCallback,
- List<Pair<IBinder, Integer>> topActivities) {
+ List<ActivityAssistInfo> topActivities) {
if (mBound) {
if (!mFullyBound) {
mFullyBound = mContext.bindServiceAsUser(mBindIntent, mFullConnection,
@@ -216,7 +217,7 @@
int topActivitiesCount = topActivities.size();
final ArrayList<IBinder> topActivitiesToken = new ArrayList<>(topActivitiesCount);
for (int i = 0; i < topActivitiesCount; i++) {
- topActivitiesToken.add(topActivities.get(i).first);
+ topActivitiesToken.add(topActivities.get(i).getActivityToken());
}
mAssistDataRequester.requestAssistData(topActivitiesToken,
fetchData,
@@ -243,8 +244,16 @@
} else {
doHandleAssistWithoutData(topActivities);
}
- } else if (showCallback != null) {
- mPendingShowCallbacks.add(showCallback);
+ } else {
+ if (showCallback != null) {
+ mPendingShowCallbacks.add(showCallback);
+ }
+ if (!assistDataRequestNeeded) {
+ // If no data are required we are not passing trough mAssistDataRequester. As
+ // a consequence, when a new session is delivered it is needed to process those
+ // requests manually.
+ mPendingHandleAssistWithoutData = topActivities;
+ }
}
mCallback.onSessionShown(this);
return true;
@@ -258,17 +267,17 @@
return false;
}
- private void doHandleAssistWithoutData(List<Pair<IBinder, Integer>> topActivities) {
+ private void doHandleAssistWithoutData(List<ActivityAssistInfo> topActivities) {
final int activityCount = topActivities.size();
for (int i = 0; i < activityCount; i++) {
- final Pair<IBinder, Integer> topActivity = topActivities.get(i);
- final IBinder activityId = topActivity.first;
- final int taskId = topActivity.second;
+ final ActivityAssistInfo topActivity = topActivities.get(i);
+ final IBinder assistToken = topActivity.getAssistToken();
+ final int taskId = topActivity.getTaskId();
final int activityIndex = i;
try {
mSession.handleAssist(
taskId,
- activityId,
+ assistToken,
/* assistData = */ null,
/* assistStructure = */ null,
/* assistContent = */ null,
@@ -468,6 +477,10 @@
} catch (RemoteException e) {
}
mAssistDataRequester.processPendingAssistData();
+ if (!mPendingHandleAssistWithoutData.isEmpty()) {
+ doHandleAssistWithoutData(mPendingHandleAssistWithoutData);
+ mPendingHandleAssistWithoutData.clear();
+ }
}
return true;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index cc49e2d..3db31ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4254,6 +4254,14 @@
public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT =
KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int";
+ /**
+ * Specifies the RCS feature tag allowed for the carrier.
+ *
+ * <p>The values refer to RCC.07 2.4.4.
+ */
+ public static final String KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY =
+ KEY_PREFIX + "rcs_feature_tag_allowed_string_array";
+
private Ims() {}
private static PersistableBundle getDefaults() {
@@ -4267,6 +4275,27 @@
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
+ defaults.putStringArray(KEY_RCS_FEATURE_TAG_ALLOWED_STRING_ARRAY, new String[]{
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.msg\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.largemsg\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.deferred\"",
+ "+g.gsma.rcs.cpm.pager-large",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.fthttp\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.ftsms\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callcomposer\"",
+ "+g.gsma.callcomposer",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.callunanswered\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedmap\"",
+ "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.gsma.sharedsketch\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geopush\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.geosms\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"",
+ "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot.sa\"",
+ "+g.gsma.rcs.botversion=\"#=1,#=2\"",
+ "+g.gsma.rcs.cpimext"});
+
return defaults;
}
}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index f110dae..2d06062 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -564,6 +564,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public int getDataRegState() {
return mDataRegState;
}
diff --git a/telephony/java/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index 2f89bfb..88c66ac 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -84,7 +84,7 @@
* </ul>
* One of the use case is that UX can show a different icon, for example, "5G+"
*/
- public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 4;
+ public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
@NetworkType
private final int mNetworkType;
@@ -186,7 +186,8 @@
case OVERRIDE_NETWORK_TYPE_LTE_CA: return "LTE_CA";
case OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO: return "LTE_ADV_PRO";
case OVERRIDE_NETWORK_TYPE_NR_NSA: return "NR_NSA";
- case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE: return "NR_NSA_MMWAVE";
+ case OVERRIDE_NETWORK_TYPE_NR_ADVANCED: return "NR_ADVANCED";
default: return "UNKNOWN";
}
}
diff --git a/telephony/java/android/telephony/data/NrQos.java b/telephony/java/android/telephony/data/NrQos.java
index 2011eed..fe124ac 100644
--- a/telephony/java/android/telephony/data/NrQos.java
+++ b/telephony/java/android/telephony/data/NrQos.java
@@ -50,6 +50,18 @@
return new NrQos(in);
}
+ public int get5Qi() {
+ return fiveQi;
+ }
+
+ public int getQfi() {
+ return qosFlowId;
+ }
+
+ public int getAveragingWindow() {
+ return averagingWindowMs;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(Qos.QOS_TYPE_NR, dest, flags);
diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
new file mode 100644
index 0000000..fd3bbb0
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.telephony.data;
+
+ parcelable NrQosSessionAttributes;
diff --git a/telephony/java/android/telephony/data/NrQosSessionAttributes.java b/telephony/java/android/telephony/data/NrQosSessionAttributes.java
new file mode 100644
index 0000000..857ccb9
--- /dev/null
+++ b/telephony/java/android/telephony/data/NrQosSessionAttributes.java
@@ -0,0 +1,256 @@
+/*
+ * 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.telephony.data;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.QosSessionAttributes;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides Qos attributes of an NR bearer.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class NrQosSessionAttributes implements Parcelable, QosSessionAttributes {
+ private static final String TAG = NrQosSessionAttributes.class.getSimpleName();
+ private final int m5Qi;
+ private final int mQfi;
+ private final long mMaxUplinkBitRate;
+ private final long mMaxDownlinkBitRate;
+ private final long mGuaranteedUplinkBitRate;
+ private final long mGuaranteedDownlinkBitRate;
+ private final long mAveragingWindow;
+ @NonNull private final List<InetSocketAddress> mRemoteAddresses;
+
+ /**
+ * 5G QOS Identifier (5QI), see 3GPP TS 24.501 and 23.501.
+ * The allowed values are standard values(1-9, 65-68, 69-70, 75, 79-80, 82-85)
+ * defined in the spec and operator specific values in the range 128-254.
+ *
+ * @return the 5QI of the QOS flow
+ */
+ public int get5Qi() {
+ return m5Qi;
+ }
+
+ /**
+ * QOS flow identifier of the QOS flow description in the
+ * range of 1 to 63. see 3GPP TS 24.501 and 23.501.
+ *
+ * @return the QOS flow identifier of the session
+ */
+ public int getQfi() {
+ return mQfi;
+ }
+
+ /**
+ * Minimum bit rate in kbps that is guaranteed to be provided by the network on the uplink.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the guaranteed bit rate in kbps
+ */
+ public long getGuaranteedUplinkBitRate() {
+ return mGuaranteedUplinkBitRate;
+ }
+
+ /**
+ * Minimum bit rate in kbps that is guaranteed to be provided by the network on the downlink.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the guaranteed bit rate in kbps
+ */
+ public long getGuaranteedDownlinkBitRate() {
+ return mGuaranteedDownlinkBitRate;
+ }
+
+ /**
+ * The maximum uplink kbps that the network will accept.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the max uplink bit rate in kbps
+ */
+ public long getMaxUplinkBitRate() {
+ return mMaxUplinkBitRate;
+ }
+
+ /**
+ * The maximum downlink kbps that the network can provide.
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * Note: The Qos Session may be shared with OTHER applications besides yours.
+ *
+ * @return the max downlink bit rate in kbps
+ */
+ public long getMaxDownlinkBitRate() {
+ return mMaxDownlinkBitRate;
+ }
+
+ /**
+ * The duration in milliseconds over which the maximum bit rates and guaranteed bit rates
+ * are calculated
+ *
+ * see 3GPP TS 24.501 section 6.2.5
+ *
+ * @return the averaging window duration in milliseconds
+ */
+ public long getAveragingWindow() {
+ return mAveragingWindow;
+ }
+
+ /**
+ * List of remote addresses associated with the Qos Session. The given uplink bit rates apply
+ * to this given list of remote addresses.
+ *
+ * Note: In the event that the list is empty, it is assumed that the uplink bit rates apply to
+ * all remote addresses that are not contained in a different set of attributes.
+ *
+ * @return list of remote socket addresses that the attributes apply to
+ */
+ @NonNull
+ public List<InetSocketAddress> getRemoteAddresses() {
+ return mRemoteAddresses;
+ }
+
+ /**
+ * ..ctor for attributes
+ *
+ * @param fiveQi 5G quality class indicator
+ * @param qfi QOS flow identifier
+ * @param maxDownlinkBitRate the max downlink bit rate in kbps
+ * @param maxUplinkBitRate the max uplink bit rate in kbps
+ * @param guaranteedDownlinkBitRate the guaranteed downlink bit rate in kbps
+ * @param guaranteedUplinkBitRate the guaranteed uplink bit rate in kbps
+ * @param averagingWindow the averaging window duration in milliseconds
+ * @param remoteAddresses the remote addresses that the uplink bit rates apply to
+ *
+ * @hide
+ */
+ public NrQosSessionAttributes(final int fiveQi, final int qfi,
+ final long maxDownlinkBitRate, final long maxUplinkBitRate,
+ final long guaranteedDownlinkBitRate, final long guaranteedUplinkBitRate,
+ final long averagingWindow, @NonNull final List<InetSocketAddress> remoteAddresses) {
+ Objects.requireNonNull(remoteAddresses, "remoteAddress must be non-null");
+ m5Qi = fiveQi;
+ mQfi = qfi;
+ mMaxDownlinkBitRate = maxDownlinkBitRate;
+ mMaxUplinkBitRate = maxUplinkBitRate;
+ mGuaranteedDownlinkBitRate = guaranteedDownlinkBitRate;
+ mGuaranteedUplinkBitRate = guaranteedUplinkBitRate;
+ mAveragingWindow = averagingWindow;
+
+ final List<InetSocketAddress> remoteAddressesTemp = copySocketAddresses(remoteAddresses);
+ mRemoteAddresses = Collections.unmodifiableList(remoteAddressesTemp);
+ }
+
+ private static List<InetSocketAddress> copySocketAddresses(
+ @NonNull final List<InetSocketAddress> remoteAddresses) {
+ final List<InetSocketAddress> remoteAddressesTemp = new ArrayList<>();
+ for (final InetSocketAddress socketAddress : remoteAddresses) {
+ if (socketAddress != null && socketAddress.getAddress() != null) {
+ remoteAddressesTemp.add(socketAddress);
+ }
+ }
+ return remoteAddressesTemp;
+ }
+
+ private NrQosSessionAttributes(@NonNull final Parcel in) {
+ m5Qi = in.readInt();
+ mQfi = in.readInt();
+ mMaxDownlinkBitRate = in.readLong();
+ mMaxUplinkBitRate = in.readLong();
+ mGuaranteedDownlinkBitRate = in.readLong();
+ mGuaranteedUplinkBitRate = in.readLong();
+ mAveragingWindow = in.readLong();
+
+ final int size = in.readInt();
+ final List<InetSocketAddress> remoteAddresses = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ final byte[] addressBytes = in.createByteArray();
+ final int port = in.readInt();
+ try {
+ remoteAddresses.add(
+ new InetSocketAddress(InetAddress.getByAddress(addressBytes), port));
+ } catch (final UnknownHostException e) {
+ // Impossible case since its filtered out the null values in the ..ctor
+ Log.e(TAG, "unable to unparcel remote address at index: " + i, e);
+ }
+ }
+ mRemoteAddresses = Collections.unmodifiableList(remoteAddresses);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull final Parcel dest, final int flags) {
+ dest.writeInt(m5Qi);
+ dest.writeInt(mQfi);
+ dest.writeLong(mMaxDownlinkBitRate);
+ dest.writeLong(mMaxUplinkBitRate);
+ dest.writeLong(mGuaranteedDownlinkBitRate);
+ dest.writeLong(mGuaranteedUplinkBitRate);
+ dest.writeLong(mAveragingWindow);
+
+ final int size = mRemoteAddresses.size();
+ dest.writeInt(size);
+ for (int i = 0; i < size; i++) {
+ final InetSocketAddress address = mRemoteAddresses.get(i);
+ dest.writeByteArray(address.getAddress().getAddress());
+ dest.writeInt(address.getPort());
+ }
+ }
+
+ @NonNull
+ public static final Creator<NrQosSessionAttributes> CREATOR =
+ new Creator<NrQosSessionAttributes>() {
+ @NonNull
+ @Override
+ public NrQosSessionAttributes createFromParcel(@NonNull final Parcel in) {
+ return new NrQosSessionAttributes(in);
+ }
+
+ @NonNull
+ @Override
+ public NrQosSessionAttributes[] newArray(final int size) {
+ return new NrQosSessionAttributes[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6184ffe..46752b7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2351,6 +2351,16 @@
boolean getCarrierSingleRegistrationEnabled(int subId);
/**
+ * Overrides the ims feature validation result
+ */
+ boolean setImsFeatureValidationOverride(int subId, String enabled);
+
+ /**
+ * Gets the ims feature validation override value
+ */
+ boolean getImsFeatureValidationOverride(int subId);
+
+ /**
* Return the mobile provisioning url that is used to launch a browser to allow users to manage
* their mobile plan.
*/
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index f49d4fc..4259a86 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,7 +32,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -98,8 +98,8 @@
};
@DataClass.Generated(
- time = 1604522375155L,
- codegenVersion = "1.0.20",
+ time = 1616541542813L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index e8cce23..677094b 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,7 +46,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -120,8 +120,8 @@
};
@DataClass.Generated(
- time = 1604522376059L,
- codegenVersion = "1.0.20",
+ time = 1616541543730L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 9de6552..eb260ab 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -54,7 +54,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -412,8 +412,8 @@
}
@DataClass.Generated(
- time = 1604522374190L,
- codegenVersion = "1.0.20",
+ time = 1616541541942L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 5a3e273..158e065 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -23,8 +23,11 @@
import android.annotation.StringDef;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
+import android.companion.ICompanionDeviceManager;
import android.content.pm.PackageManager;
import android.net.LinkAddress;
+import android.os.Binder;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -282,6 +285,16 @@
/**
+ * Binder types are also supported
+ */
+ private @NonNull IBinder mToken = new Binder();
+ /**
+ * AIDL interface types are also supported
+ */
+ private @Nullable ICompanionDeviceManager mIPCInterface = null;
+
+
+ /**
* Manually declaring any method that would otherwise be generated suppresses its generation,
* allowing for fine-grained overrides of the generated behavior.
*/
@@ -344,7 +357,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -492,6 +505,10 @@
*
* Validation annotations following {@link Each} annotation, will be applied for each
* array/collection element instead.
+ * @param token
+ * Binder types are also supported
+ * @param iPCInterface
+ * AIDL interface types are also supported
*/
@DataClass.Generated.Member
public SampleDataClass(
@@ -514,7 +531,9 @@
@Nullable LinkAddress[] linkAddresses5,
@StringRes int stringRes,
@android.annotation.IntRange(from = 0, to = 6) int dayOfWeek,
- @Size(2) @NonNull @FloatRange(from = 0f) float[] coords) {
+ @Size(2) @NonNull @FloatRange(from = 0f) float[] coords,
+ @NonNull IBinder token,
+ @Nullable ICompanionDeviceManager iPCInterface) {
this.mNum = num;
this.mNum2 = num2;
this.mNum4 = num4;
@@ -597,6 +616,10 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -797,6 +820,22 @@
}
/**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull IBinder getToken() {
+ return mToken;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @Nullable ICompanionDeviceManager getIPCInterface() {
+ return mIPCInterface;
+ }
+
+ /**
* When using transient fields for caching it's often also a good idea to initialize them
* lazily.
*
@@ -1089,6 +1128,26 @@
return this;
}
+ /**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setToken(@NonNull IBinder value) {
+ mToken = value;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ return this;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull SampleDataClass setIPCInterface(@NonNull ICompanionDeviceManager value) {
+ mIPCInterface = value;
+ return this;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -1115,7 +1174,9 @@
"linkAddresses5 = " + java.util.Arrays.toString(mLinkAddresses5) + ", " +
"stringRes = " + mStringRes + ", " +
"dayOfWeek = " + mDayOfWeek + ", " +
- "coords = " + java.util.Arrays.toString(mCoords) +
+ "coords = " + java.util.Arrays.toString(mCoords) + ", " +
+ "token = " + mToken + ", " +
+ "iPCInterface = " + mIPCInterface +
" }";
}
@@ -1151,7 +1212,9 @@
&& java.util.Arrays.equals(mLinkAddresses5, that.mLinkAddresses5)
&& mStringRes == that.mStringRes
&& mDayOfWeek == that.mDayOfWeek
- && java.util.Arrays.equals(mCoords, that.mCoords);
+ && java.util.Arrays.equals(mCoords, that.mCoords)
+ && Objects.equals(mToken, that.mToken)
+ && Objects.equals(mIPCInterface, that.mIPCInterface);
}
@Override
@@ -1181,6 +1244,8 @@
_hash = 31 * _hash + mStringRes;
_hash = 31 * _hash + mDayOfWeek;
_hash = 31 * _hash + java.util.Arrays.hashCode(mCoords);
+ _hash = 31 * _hash + Objects.hashCode(mToken);
+ _hash = 31 * _hash + Objects.hashCode(mIPCInterface);
return _hash;
}
@@ -1208,6 +1273,8 @@
actionInt.acceptInt(this, "stringRes", mStringRes);
actionInt.acceptInt(this, "dayOfWeek", mDayOfWeek);
actionObject.acceptObject(this, "coords", mCoords);
+ actionObject.acceptObject(this, "token", mToken);
+ actionObject.acceptObject(this, "iPCInterface", mIPCInterface);
}
/** @deprecated May cause boxing allocations - use with caution! */
@@ -1234,6 +1301,8 @@
action.acceptObject(this, "stringRes", mStringRes);
action.acceptObject(this, "dayOfWeek", mDayOfWeek);
action.acceptObject(this, "coords", mCoords);
+ action.acceptObject(this, "token", mToken);
+ action.acceptObject(this, "iPCInterface", mIPCInterface);
}
@DataClass.Generated.Member
@@ -1269,6 +1338,7 @@
if (mOtherParcelable != null) flg |= 0x40;
if (mLinkAddresses4 != null) flg |= 0x800;
if (mLinkAddresses5 != null) flg |= 0x10000;
+ if (mIPCInterface != null) flg |= 0x200000;
dest.writeLong(flg);
dest.writeInt(mNum);
dest.writeInt(mNum2);
@@ -1290,6 +1360,8 @@
dest.writeInt(mStringRes);
dest.writeInt(mDayOfWeek);
dest.writeFloatArray(mCoords);
+ dest.writeStrongBinder(mToken);
+ if (mIPCInterface != null) dest.writeStrongInterface(mIPCInterface);
}
@Override
@@ -1326,6 +1398,8 @@
int stringRes = in.readInt();
int dayOfWeek = in.readInt();
float[] coords = in.createFloatArray();
+ IBinder token = (IBinder) in.readStrongBinder();
+ ICompanionDeviceManager iPCInterface = (flg & 0x200000) == 0 ? null : ICompanionDeviceManager.Stub.asInterface(in.readStrongBinder());
this.mNum = num;
this.mNum2 = num2;
@@ -1409,6 +1483,10 @@
"from", 0f);
}
+ this.mToken = token;
+ AnnotationValidations.validate(
+ NonNull.class, null, mToken);
+ this.mIPCInterface = iPCInterface;
onConstructed();
}
@@ -1454,6 +1532,8 @@
private @StringRes int mStringRes;
private @android.annotation.IntRange(from = 0, to = 6) int mDayOfWeek;
private @Size(2) @NonNull @FloatRange(from = 0f) float[] mCoords;
+ private @NonNull IBinder mToken;
+ private @Nullable ICompanionDeviceManager mIPCInterface;
private long mBuilderFieldsSet = 0L;
@@ -1794,10 +1874,32 @@
return this;
}
+ /**
+ * Binder types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setToken(@NonNull IBinder value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x100000;
+ mToken = value;
+ return this;
+ }
+
+ /**
+ * AIDL interface types are also supported
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIPCInterface(@NonNull ICompanionDeviceManager value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x200000;
+ mIPCInterface = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull SampleDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x100000; // Mark builder used
+ mBuilderFieldsSet |= 0x400000; // Mark builder used
if ((mBuilderFieldsSet & 0x10) == 0) {
mName2 = "Bob";
@@ -1841,6 +1943,12 @@
if ((mBuilderFieldsSet & 0x80000) == 0) {
mCoords = new float[] { 0f, 0f };
}
+ if ((mBuilderFieldsSet & 0x100000) == 0) {
+ mToken = new Binder();
+ }
+ if ((mBuilderFieldsSet & 0x200000) == 0) {
+ mIPCInterface = null;
+ }
SampleDataClass o = new SampleDataClass(
mNum,
mNum2,
@@ -1861,12 +1969,14 @@
mLinkAddresses5,
mStringRes,
mDayOfWeek,
- mCoords);
+ mCoords,
+ mToken,
+ mIPCInterface);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x100000) != 0) {
+ if ((mBuilderFieldsSet & 0x400000) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -1874,10 +1984,10 @@
}
@DataClass.Generated(
- time = 1604522372172L,
- codegenVersion = "1.0.20",
+ time = 1616541539978L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
- inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
+ inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final int STATE_UNDEFINED\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange int mDayOfWeek\nprivate @android.annotation.Size @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange float[] mCoords\nprivate @android.annotation.NonNull android.os.IBinder mToken\nprivate @android.annotation.Nullable android.companion.ICompanionDeviceManager mIPCInterface\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index 3ab3445..a535e22 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,7 +85,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -253,8 +253,8 @@
}
@DataClass.Generated(
- time = 1604522373190L,
- codegenVersion = "1.0.20",
+ time = 1616541540898L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
index 8901cac..d409624 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java
@@ -36,7 +36,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -135,8 +135,8 @@
};
@DataClass.Generated(
- time = 1604522377998L,
- codegenVersion = "1.0.20",
+ time = 1616541545539L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBar\nclass NestedDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -160,7 +160,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -259,8 +259,8 @@
};
@DataClass.Generated(
- time = 1604522378007L,
- codegenVersion = "1.0.20",
+ time = 1616541545548L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull long mBaz2\nclass NestedDataClass3 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
@@ -274,7 +274,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -373,8 +373,8 @@
};
@DataClass.Generated(
- time = 1604522378015L,
- codegenVersion = "1.0.20",
+ time = 1616541545552L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithNestedDataClasses.java",
inputSignatures = " @android.annotation.NonNull java.lang.String mBaz\nclass NestedDataClass2 extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index ac776f3..3583b95 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -64,7 +64,7 @@
- // Code below generated by codegen v1.0.20.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -89,8 +89,8 @@
}
@DataClass.Generated(
- time = 1604522377011L,
- codegenVersion = "1.0.20",
+ time = 1616541544639L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "private @android.annotation.Nullable java.util.List<java.util.Set<?>> mUsesWildcards\npublic @android.annotation.NonNull java.lang.String someMethod(int)\nprivate @android.annotation.IntRange void annotatedWithConstArg()\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tests/StagedInstallTest/Android.bp b/tests/StagedInstallTest/Android.bp
index 2e57467..c679d04 100644
--- a/tests/StagedInstallTest/Android.bp
+++ b/tests/StagedInstallTest/Android.bp
@@ -25,17 +25,24 @@
name: "StagedInstallInternalTestApp",
manifest: "app/AndroidManifest.xml",
srcs: ["app/src/**/*.java"],
- static_libs: ["androidx.test.rules", "cts-install-lib"],
+ static_libs: [
+ "androidx.test.rules",
+ "cts-install-lib",
+ ],
test_suites: ["general-tests"],
java_resources: [
":com.android.apex.apkrollback.test_v2",
+ ":StagedInstallTestApexV2_WrongSha",
],
}
java_test_host {
name: "StagedInstallInternalTest",
srcs: ["src/**/*.java"],
- libs: ["tradefed", "cts-shim-host-lib"],
+ libs: [
+ "tradefed",
+ "cts-shim-host-lib",
+ ],
static_libs: [
"testng",
"compatibility-tradefed",
diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
index ad8aac1..e633c87 100644
--- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java
@@ -17,6 +17,7 @@
package com.android.tests.stagedinstallinternal;
import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -50,6 +51,9 @@
private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+ private static final TestApp APEX_WRONG_SHA_V2 = new TestApp(
+ "ApexWrongSha2", SHIM_APEX_PACKAGE_NAME, 2, /*isApex*/true,
+ "com.android.apex.cts.shim.v2_wrong_sha.apex");
private File mTestStateFile = new File(
InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
@@ -128,6 +132,26 @@
}
@Test
+ public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
+ InstallUtils.commitExpectingFailure(AssertionError.class, "apexd verification failed",
+ Install.single(APEX_WRONG_SHA_V2).setStaged());
+ }
+
+ @Test
+ public void testStagedSessionShouldCleanUpOnOnSuccess_Commit() throws Exception {
+ int sessionId = Install.single(TestApp.A1).setStaged().commit();
+ storeSessionId(sessionId);
+ }
+
+ @Test
+ public void testStagedSessionShouldCleanUpOnOnSuccess_Verify() throws Exception {
+ int sessionId = retrieveLastSessionId();
+ PackageInstaller.SessionInfo info = InstallUtils.getStagedSessionInfo(sessionId);
+ assertThat(info).isNotNull();
+ assertThat(info.isStagedSessionApplied()).isTrue();
+ }
+
+ @Test
public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK",
Install.single(TestApp.AIncompleteSplit).setStaged());
diff --git a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
index 8dc53ac..9fd190c 100644
--- a/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
+++ b/tests/StagedInstallTest/src/com/android/tests/stagedinstallinternal/host/StagedInstallInternalTest.java
@@ -44,7 +44,6 @@
import org.junit.runner.RunWith;
import java.io.File;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -252,6 +251,28 @@
}
@Test
+ public void testStagedSessionShouldCleanUpOnVerificationFailure() throws Exception {
+ assumeTrue("Device does not support updating APEX",
+ mHostUtils.isApexUpdateSupported());
+ List<String> before = getStagingDirectories();
+ runPhase("testStagedSessionShouldCleanUpOnVerificationFailure");
+ List<String> after = getStagingDirectories();
+ assertThat(after).isEqualTo(before);
+ }
+
+ @Test
+ @LargeTest
+ public void testStagedSessionShouldCleanUpOnOnSuccess() throws Exception {
+ List<String> before = getStagingDirectories();
+ runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Commit");
+ assertThat(getStagingDirectories()).isNotEqualTo(before);
+ getDevice().reboot();
+ runPhase("testStagedSessionShouldCleanUpOnOnSuccess_Verify");
+ List<String> after = getStagingDirectories();
+ assertThat(after).isEqualTo(before);
+ }
+
+ @Test
public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception {
List<String> before = getStagingDirectories();
runPhase("testStagedInstallationShouldCleanUpOnValidationFailure");
@@ -273,12 +294,14 @@
//create random directories in /data/app-staging folder
getDevice().enableAdbRoot();
getDevice().executeShellCommand("mkdir /data/app-staging/session_123");
- getDevice().executeShellCommand("mkdir /data/app-staging/random_name");
+ getDevice().executeShellCommand("mkdir /data/app-staging/session_456");
getDevice().disableAdbRoot();
- assertThat(getStagingDirectories()).isNotEmpty();
+ assertThat(getStagingDirectories()).contains("session_123");
+ assertThat(getStagingDirectories()).contains("session_456");
getDevice().reboot();
- assertThat(getStagingDirectories()).isEmpty();
+ assertThat(getStagingDirectories()).doesNotContain("session_123");
+ assertThat(getStagingDirectories()).doesNotContain("session_456");
}
@Test
@@ -304,9 +327,6 @@
.stream().filter(entry -> entry.getName().matches("session_\\d+"))
.map(entry -> entry.getName())
.collect(Collectors.toList());
- } catch (Exception e) {
- // Return an empty list if any error
- return Collections.EMPTY_LIST;
} finally {
getDevice().disableAdbRoot();
}
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index fd126ad..1e54093 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertParcelSane
@@ -44,7 +45,13 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 12)
+ if (isAtLeastS()) {
+ // From S, the config will have 12 items
+ assertParcelSane(config, 12)
+ } else {
+ // For R or below, the config will have 10 items
+ assertParcelSane(config, 10)
+ }
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java
index 11d44b8..cd9da8e 100644
--- a/tests/net/common/java/android/net/NetworkTest.java
+++ b/tests/net/common/java/android/net/NetworkTest.java
@@ -22,11 +22,16 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.os.Build;
import android.platform.test.annotations.AppModeFull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -43,6 +48,9 @@
public class NetworkTest {
final Network mNetwork = new Network(99);
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
@Test
public void testBindSocketOfInvalidFdThrows() throws Exception {
@@ -151,6 +159,23 @@
}
@Test
+ public void testFromNetworkHandle() {
+ final Network network = new Network(1234);
+ assertEquals(network.getNetId(),
+ Network.fromNetworkHandle(network.getNetworkHandle()).getNetId());
+ }
+
+ // Parsing private DNS bypassing handle was not supported until S
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testFromNetworkHandle_S() {
+ final Network network = new Network(1234, true);
+
+ final Network recreatedNetwork = Network.fromNetworkHandle(network.getNetworkHandle());
+ assertEquals(network.netId, recreatedNetwork.netId);
+ assertEquals(network.getNetIdForResolv(), recreatedNetwork.getNetIdForResolv());
+ }
+
+ @Test
public void testGetPrivateDnsBypassingCopy() {
final Network copy = mNetwork.getPrivateDnsBypassingCopy();
assertEquals(mNetwork.netId, copy.netId);
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 2af4117..44298d4 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -30,10 +30,13 @@
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE;
-import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
@@ -91,10 +94,6 @@
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_DATA_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_BATTERY_SAVER;
-import static android.net.NetworkPolicyManager.BLOCKED_REASON_NONE;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
@@ -266,6 +265,7 @@
import android.system.Os;
import android.telephony.TelephonyManager;
import android.telephony.data.EpsBearerQosSessionAttributes;
+import android.telephony.data.NrQosSessionAttributes;
import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -1201,12 +1201,10 @@
mNetworkCapabilities);
mMockNetworkAgent.waitForIdle(TIMEOUT_MS);
- final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
- : mMockVpn.getNetwork().getNetId();
- verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+ verify(mMockNetd, times(1)).networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(uids)));
verify(mMockNetd, never())
- .networkRemoveUidRanges(eq(expectedNetId), any());
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()), any());
mAgentRegistered = true;
updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent");
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
@@ -1573,7 +1571,7 @@
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(mSystemProperties).when(deps).getSystemProperties();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
- doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
+ doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any());
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -9799,14 +9797,13 @@
exemptUidCaptor.capture());
assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid);
- final int expectedNetId = mMockVpn.getNetwork() == null ? NETID_UNSET
- : mMockVpn.getNetwork().getNetId();
-
if (add) {
- inOrder.verify(mMockNetd, times(1)).networkAddUidRanges(eq(expectedNetId),
+ inOrder.verify(mMockNetd, times(1))
+ .networkAddUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
} else {
- inOrder.verify(mMockNetd, times(1)).networkRemoveUidRanges(eq(expectedNetId),
+ inOrder.verify(mMockNetd, times(1))
+ .networkRemoveUidRanges(eq(mMockVpn.getNetwork().getNetId()),
eq(toUidRangeStableParcels(vpnRanges)));
}
@@ -9967,7 +9964,7 @@
&& session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));
mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
- .sendQosSessionLost(qosCallbackId, sessionId);
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
waitForIdle();
verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
session.getSessionId() == sessionId
@@ -9975,6 +9972,36 @@
}
@Test
+ public void testNrQosCallbackAvailableAndLost() throws Exception {
+ mQosCallbackMockHelper = new QosCallbackMockHelper();
+ final int sessionId = 10;
+ final int qosCallbackId = 1;
+
+ when(mQosCallbackMockHelper.mFilter.validate())
+ .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE);
+ mQosCallbackMockHelper.registerQosCallback(
+ mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
+ waitForIdle();
+
+ final NrQosSessionAttributes attributes = new NrQosSessionAttributes(
+ 1, 2, 3, 4, 5, 6, 7, new ArrayList<>());
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
+ waitForIdle();
+
+ verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes));
+
+ mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
+ .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER);
+ waitForIdle();
+ verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session ->
+ session.getSessionId() == sessionId
+ && session.getSessionType() == QosSession.TYPE_NR_BEARER));
+ }
+
+ @Test
public void testQosCallbackTooManyRequests() throws Exception {
mQosCallbackMockHelper = new QosCallbackMockHelper();
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8c5d1d6..97c65eb 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -22,7 +22,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -56,6 +58,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -80,6 +83,12 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
@@ -263,14 +272,6 @@
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
ApfStats apfStats = new ApfStats.Builder()
.setDurationMs(45000)
.setReceivedRas(10)
@@ -584,11 +585,21 @@
return buffer.toString();
}
- void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
- mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? mNcWifi : mNcCell);
+ }
+
+ void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
+ setCapabilities(netId);
+ mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..52975e4 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -23,8 +23,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
@@ -61,18 +63,16 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
+ final NetworkCapabilities mNcWifi = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ final NetworkCapabilities mNcCell = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
mCm = mock(ConnectivityManager.class);
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
mService = new NetdEventListenerService(mCm);
}
@@ -470,7 +470,16 @@
assertEquals(want, got);
}
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? mNcWifi : mNcCell);
+ }
+
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+ setCapabilities(netId);
return new Thread(() -> {
try {
mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
@@ -481,6 +490,7 @@
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
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..bcd6ed7 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,9 +59,10 @@
"android.view.RoundedCornersTest",
"android.view.WindowMetricsTest",
"android.view.PendingInsetsControllerTest",
- "android.app.WindowContextTest",
+ "android.window.WindowContextTest",
"android.window.WindowMetricsHelperTest",
- "android.app.activity.ActivityThreadTest"
+ "android.app.activity.ActivityThreadTest",
+ "android.window.WindowContextControllerTest"
};
public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index a9d5822..babea36 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -19,6 +19,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot;
import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback;
@@ -238,9 +240,14 @@
doReturn(Collections.singletonList(TEST_SUBSCRIPTION_INFO))
.when(mSubMgr)
.getSubscriptionsInGroup(any());
- doReturn(isPrivileged)
+ doReturn(mTelMgr)
.when(mTelMgr)
- .hasCarrierPrivileges(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+ .createForSubscriptionId(eq(TEST_SUBSCRIPTION_INFO.getSubscriptionId()));
+ doReturn(isPrivileged
+ ? CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
+ : CARRIER_PRIVILEGE_STATUS_NO_ACCESS)
+ .when(mTelMgr)
+ .checkCarrierPrivilegesForPackage(eq(TEST_PACKAGE_NAME));
}
@Test
@@ -391,7 +398,7 @@
mTestLooper.moveTimeForward(
VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2);
mTestLooper.dispatchAll();
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2);
// Verify that new instance was different, and the old one was torn down
@@ -492,7 +499,7 @@
doReturn(Process.SYSTEM_UID).when(mMockDeps).getBinderCallingUid();
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected IllegalStateException exception for system server");
} catch (IllegalStateException expected) {
}
@@ -505,7 +512,7 @@
.getBinderCallingUid();
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected security exception for non system user");
} catch (SecurityException expected) {
}
@@ -516,15 +523,24 @@
setupMockedCarrierPrivilege(false);
try {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
fail("Expected security exception for missing carrier privileges");
} catch (SecurityException expected) {
}
}
@Test
+ public void testClearVcnConfigMismatchedPackages() throws Exception {
+ try {
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, "IncorrectPackage");
+ fail("Expected security exception due to mismatched packages");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
public void testClearVcnConfig() throws Exception {
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME);
assertTrue(mVcnMgmtSvc.getConfigs().isEmpty());
verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class));
}
@@ -535,7 +551,7 @@
mVcnMgmtSvc.registerVcnStatusCallback(TEST_UUID_2, mMockStatusCallback, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_ACTIVE);
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(mMockStatusCallback).onVcnStatusChanged(VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED);
}
@@ -564,7 +580,7 @@
verify(vcnInstance).updateConfig(TEST_VCN_CONFIG);
// Verify Vcn is stopped if it was already started
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(vcnInstance).teardownAsynchronously();
}
@@ -781,7 +797,7 @@
mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME);
mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener);
- mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2);
+ mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME);
verify(mMockPolicyListener).onPolicyChanged();
}
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index 02ebaef..74392dd 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -220,11 +220,12 @@
isBinder(FieldInnerType!!) -> "BinderList"
else -> "ParcelableList"
}
+ isStrongBinder(Type) -> "StrongBinder"
isIInterface(Type) -> "StrongInterface"
- isBinder(Type) -> "StrongBinder"
else -> "TypedObject"
}.capitalize()
+ private fun isStrongBinder(type: String) = type == "Binder" || type == "IBinder"
private fun isBinder(type: String) = type == "Binder" || type == "IBinder" || isIInterface(type)
private fun isIInterface(type: String) = type.length >= 2 && type[0] == 'I' && type[1].isUpperCase()
}
\ No newline at end of file
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index d9ad649..4da4019 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.22"
+const val CODEGEN_VERSION = "1.0.23"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index c64f4bc..da0571ba 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -101,6 +101,7 @@
// Cached wificond binder handlers.
private IWificond mWificond;
+ private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
@@ -114,6 +115,18 @@
private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
/**
+ * Interface used to listen country code event
+ */
+ public interface CountryCodeChangeListener {
+ /**
+ * Called when country code changed.
+ *
+ * @param countryCode A new country code which is 2-Character alphanumeric.
+ */
+ void onChanged(@NonNull String countryCode);
+ }
+
+ /**
* Interface used when waiting for scans to be completed (with results).
*/
public interface ScanEventCallback {
@@ -147,6 +160,46 @@
void onPnoRequestFailed();
}
+ /** @hide */
+ @VisibleForTesting
+ public class WificondEventHandler extends IWificondEventCallback.Stub {
+ private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder =
+ new HashMap<>();
+
+ /**
+ * Register CountryCodeChangeListener with pid.
+ *
+ * @param executor The Executor on which to execute the callbacks.
+ * @param listener listener for country code changed events.
+ */
+ public void registerCountryCodeChangeListener(Executor executor,
+ CountryCodeChangeListener listener) {
+ mCountryCodeChangeListenerHolder.put(listener, executor);
+ }
+
+ /**
+ * Unregister CountryCodeChangeListener with pid.
+ *
+ * @param listener listener which registered country code changed events.
+ */
+ public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) {
+ mCountryCodeChangeListenerHolder.remove(listener);
+ }
+
+ @Override
+ public void OnRegDomainChanged(String countryCode) {
+ Log.d(TAG, "OnRegDomainChanged " + countryCode);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mCountryCodeChangeListenerHolder.forEach((listener, executor) -> {
+ executor.execute(() -> listener.onChanged(countryCode));
+ });
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ }
+
private class ScanEventHandler extends IScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
@@ -347,6 +400,12 @@
mWificond = wificond;
}
+ /** @hide */
+ @VisibleForTesting
+ public WificondEventHandler getWificondEventHandler() {
+ return mWificondEventHandler;
+ }
+
private class PnoScanEventHandler extends IPnoScanEvent.Stub {
private Executor mExecutor;
private ScanEventCallback mCallback;
@@ -574,6 +633,7 @@
}
try {
mWificond.asBinder().linkToDeath(() -> binderDied(), 0);
+ mWificond.registerWificondEventCallback(mWificondEventHandler);
} catch (RemoteException e) {
Log.e(TAG, "Failed to register death notification for wificond");
// The remote has already died.
@@ -1174,6 +1234,34 @@
}
/**
+ * Register the provided listener for country code event.
+ *
+ * @param executor The Executor on which to execute the callbacks.
+ * @param listener listener for country code changed events.
+ * @return true on success, false on failure.
+ */
+ public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CountryCodeChangeListener listener) {
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+ Log.d(TAG, "registerCountryCodeEventListener called");
+ mWificondEventHandler.registerCountryCodeChangeListener(executor, listener);
+ return true;
+ }
+
+
+ /**
+ * Unregister CountryCodeChangeListener with pid.
+ *
+ * @param listener listener which registered country code changed events.
+ */
+ public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) {
+ Log.d(TAG, "unregisterCountryCodeEventListener called");
+ mWificondEventHandler.unregisterCountryCodeChangeListener(listener);
+ }
+
+ /**
* Register the provided callback handler for SoftAp events. The interface must first be created
* using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
* the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 4b03a49..98a0042 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -30,6 +30,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -97,14 +98,20 @@
@Mock
private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
@Mock
+ private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener;
+ @Mock
+ private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2;
+ @Mock
private Context mContext;
private TestLooper mLooper;
private TestAlarmManager mTestAlarmManager;
private AlarmManager mAlarmManager;
private WifiNl80211Manager mWificondControl;
+ private WifiNl80211Manager.WificondEventHandler mWificondEventHandler;
private static final String TEST_INTERFACE_NAME = "test_wlan_if";
private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+ private static final String TEST_COUNTRY_CODE = "US";
private static final byte[] TEST_SSID =
new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
private static final byte[] TEST_PSK =
@@ -182,6 +189,7 @@
when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+ mWificondEventHandler = mWificondControl.getWificondEventHandler();
assertEquals(true,
mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
mNormalScanCallback, mPnoScanCallback));
@@ -760,6 +768,28 @@
}
/**
+ * Ensures callback works after register CountryCodeChangeListener.
+ */
+ @Test
+ public void testCountryCodeChangeListenerInvocation() throws Exception {
+ assertTrue(mWificondControl.registerCountryCodeChangeListener(
+ Runnable::run, mCountryCodeChangeListener));
+ assertTrue(mWificondControl.registerCountryCodeChangeListener(
+ Runnable::run, mCountryCodeChangeListener2));
+
+ mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE);
+
+ reset(mCountryCodeChangeListener);
+ reset(mCountryCodeChangeListener2);
+ mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2);
+ mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE);
+ verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE);
+ }
+
+ /**
* Verifies registration and invocation of wificond death handler.
*/
@Test