Merge "Fix size calculation" into sc-dev
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 284e807..23dc720 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -123,13 +123,19 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/public/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/public/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -137,21 +143,18 @@
],
}
-priv_apps =
- " --show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+priv_apps = " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
"\\)"
-priv_apps_in_stubs =
- " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
+priv_apps_in_stubs = " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" +
"\\)"
test = " --show-annotation android.annotation.TestApi"
-module_libs =
- " --show-annotation android.annotation.SystemApi\\(" +
- "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
+module_libs = " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" +
"\\)"
droidstubs {
@@ -166,7 +169,7 @@
last_released: {
api_file: ":android-non-updatable.api.system.latest",
removed_api_file: ":android-non-updatable-removed.api.system.latest",
- baseline_file: ":android-non-updatable-incompatibilities.api.system.latest"
+ baseline_file: ":android-non-updatable-incompatibilities.api.system.latest",
},
api_lint: {
enabled: true,
@@ -176,13 +179,19 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/system/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -206,25 +215,37 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/test/api",
dest: "android.txt",
tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/test/api",
dest: "removed.txt",
tag: ".removed-api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/test/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/test/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -252,13 +273,19 @@
},
dists: [
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
dir: "apistubs/android/module-lib/api",
dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
@@ -318,10 +345,13 @@
java_version: "1.8",
compile_dex: true,
dist: {
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
tag: ".jar",
dest: "android-non-updatable.jar",
- }
+ },
}
java_library_static {
@@ -337,7 +367,7 @@
java_library_static {
name: "android-non-updatable.stubs.system",
defaults: ["android-non-updatable_defaults_stubs_current"],
- srcs: [ ":system-api-stubs-docs-non-updatable" ],
+ srcs: [":system-api-stubs-docs-non-updatable"],
libs: modules_system_stubs,
dist: {
dir: "apistubs/android/system",
@@ -380,7 +410,10 @@
java_defaults {
name: "android_stubs_dists_default",
dist: {
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
tag: ".jar",
dest: "android.jar",
},
@@ -411,7 +444,10 @@
dists: [
{
// Legacy dist path
- targets: ["sdk", "win_sdk"],
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
tag: ".jar",
dest: "android_system.jar",
},
@@ -433,14 +469,6 @@
dist: {
dir: "apistubs/android/test",
},
- dists: [
- {
- // Legacy dist path
- targets: ["sdk", "win_sdk"],
- tag: ".jar",
- dest: "android_test.jar",
- },
- ],
}
java_library_static {
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
index 35cea3e..6c62426 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java
@@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
/**
* Provides results for AppSearch batch operations which encompass multiple documents.
@@ -180,7 +181,7 @@
public Builder<KeyType, ValueType> setSuccess(
@NonNull KeyType key, @Nullable ValueType result) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return setResult(key, AppSearchResult.newSuccessfulResult(result));
}
@@ -198,7 +199,7 @@
@AppSearchResult.ResultCode int resultCode,
@Nullable String errorMessage) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage));
}
@@ -214,8 +215,8 @@
public Builder<KeyType, ValueType> setResult(
@NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(result);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(result);
if (result.isSuccess()) {
mSuccesses.put(key, result.getResultValue());
mFailures.remove(key);
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
index b66837d..b06e215 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java
@@ -37,6 +37,8 @@
* @param <ValueType> The type of result object for successful calls.
*/
public final class AppSearchResult<ValueType> implements Parcelable {
+ private static final String TAG = "AppSearchResult";
+
/**
* Result codes from {@link AppSearchSession} methods.
* @hide
@@ -246,14 +248,22 @@
@NonNull
public static <ValueType> AppSearchResult<ValueType> throwableToFailedResult(
@NonNull Throwable t) {
- Log.d("AppSearchResult", "Converting throwable to failed result.", t);
+ // Log for traceability. NOT_FOUND is logged at VERBOSE because this error can occur during
+ // the regular operation of the system (b/183550974). Everything else is logged at DEBUG.
+ if (t instanceof AppSearchException
+ && ((AppSearchException) t).getResultCode() == RESULT_NOT_FOUND) {
+ Log.v(TAG, "Converting throwable to failed result: " + t);
+ } else {
+ Log.d(TAG, "Converting throwable to failed result.", t);
+ }
if (t instanceof AppSearchException) {
return ((AppSearchException) t).toAppSearchResult();
}
+ String exceptionClass = t.getClass().getSimpleName();
@AppSearchResult.ResultCode int resultCode;
- if (t instanceof IllegalStateException) {
+ if (t instanceof IllegalStateException || t instanceof NullPointerException) {
resultCode = AppSearchResult.RESULT_INTERNAL_ERROR;
} else if (t instanceof IllegalArgumentException) {
resultCode = AppSearchResult.RESULT_INVALID_ARGUMENT;
@@ -262,6 +272,6 @@
} else {
resultCode = AppSearchResult.RESULT_UNKNOWN_ERROR;
}
- return AppSearchResult.newFailedResult(resultCode, t.getMessage());
+ return AppSearchResult.newFailedResult(resultCode, exceptionClass + ": " + t.getMessage());
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
index ac91bdb..c85c4c3 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSession.java
@@ -19,9 +19,9 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.appsearch.exceptions.AppSearchException;
import android.app.appsearch.util.SchemaMigrationUtil;
import android.os.Bundle;
-import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArrayMap;
@@ -274,12 +274,14 @@
mService.putDocuments(mPackageName, mDatabaseName, documentBundles, mUserId,
/*binderCallStartTimeMillis=*/ SystemClock.elapsedRealtime(),
new IAppSearchBatchResultCallback.Stub() {
+ @Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> callback.onResult(result));
}
- public void onSystemError(ParcelableException exception) {
- executor.execute(() -> callback.onSystemError(exception.getCause()));
+ @Override
+ public void onSystemError(AppSearchResult result) {
+ executor.execute(() -> sendSystemErrorToCallback(result, callback));
}
});
mIsMutated = true;
@@ -321,6 +323,7 @@
request.getProjectionsInternal(),
mUserId,
new IAppSearchBatchResultCallback.Stub() {
+ @Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> {
AppSearchBatchResult.Builder<String, GenericDocument>
@@ -359,8 +362,9 @@
});
}
- public void onSystemError(ParcelableException exception) {
- executor.execute(() -> callback.onSystemError(exception.getCause()));
+ @Override
+ public void onSystemError(AppSearchResult result) {
+ executor.execute(() -> sendSystemErrorToCallback(result, callback));
}
});
} catch (RemoteException e) {
@@ -515,12 +519,14 @@
mService.removeByUri(mPackageName, mDatabaseName, request.getNamespace(),
new ArrayList<>(request.getUris()), mUserId,
new IAppSearchBatchResultCallback.Stub() {
+ @Override
public void onResult(AppSearchBatchResult result) {
executor.execute(() -> callback.onResult(result));
}
- public void onSystemError(ParcelableException exception) {
- executor.execute(() -> callback.onSystemError(exception.getCause()));
+ @Override
+ public void onSystemError(AppSearchResult result) {
+ executor.execute(() -> sendSystemErrorToCallback(result, callback));
}
});
mIsMutated = true;
@@ -817,4 +823,21 @@
}
});
}
+
+ /**
+ * Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given
+ * failed {@link AppSearchResult}.
+ *
+ * <p>The {@link AppSearchResult} generally comes from
+ * {@link IAppSearchBatchResultCallback#onSystemError}.
+ *
+ * <p>This method should be called from the callback executor thread.
+ */
+ private void sendSystemErrorToCallback(
+ @NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) {
+ Preconditions.checkArgument(!failedResult.isSuccess());
+ Throwable throwable = new AppSearchException(
+ failedResult.getResultCode(), failedResult.getErrorMessage());
+ callback.onSystemError(throwable);
+ }
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
index 49049b6..28f8a7a 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/BatchResultCallback.java
@@ -36,13 +36,23 @@
void onResult(@NonNull AppSearchBatchResult<KeyType, ValueType> result);
/**
- * Called when a system error occurred.
+ * Called when a system error occurs.
*
- * @param throwable The cause throwable.
+ * <p>This method is only called the infrastructure is fundamentally broken or unavailable, such
+ * that none of the requests could be started. For example, it will be called if the AppSearch
+ * service unexpectedly fails to initialize and can't be recovered by any means, or if
+ * communicating to the server over Binder fails (e.g. system service crashed or device is
+ * rebooting).
+ *
+ * <p>The error is not expected to be recoverable and there is no specific recommended action
+ * other than displaying a permanent message to the user.
+ *
+ * <p>Normal errors that are caused by invalid inputs or recoverable/retriable situations
+ * are reported associated with the input that caused them via the {@link #onResult} method.
+ *
+ * @param throwable an exception describing the system error
*/
default void onSystemError(@Nullable Throwable throwable) {
- if (throwable != null) {
- throw new RuntimeException(throwable);
- }
+ throw new RuntimeException("Unrecoverable system error", throwable);
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
index b1bbd18..64b331e 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchBatchResultCallback.aidl
@@ -16,10 +16,10 @@
package android.app.appsearch;
import android.app.appsearch.AppSearchBatchResult;
-import android.os.ParcelableException;
+import android.app.appsearch.AppSearchResult;
/** {@hide} */
oneway interface IAppSearchBatchResultCallback {
void onResult(in AppSearchBatchResult result);
- void onSystemError(in ParcelableException exception);
-}
\ No newline at end of file
+ void onSystemError(in AppSearchResult result);
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
index 27729a5..299c9957 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchResultCallback.aidl
@@ -16,9 +16,8 @@
package android.app.appsearch;
import android.app.appsearch.AppSearchResult;
-import android.os.ParcelableException;
/** {@hide} */
oneway interface IAppSearchResultCallback {
void onResult(in AppSearchResult result);
-}
\ No newline at end of file
+}
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 a8048dc..2368bdb 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/AppSearchSchema.java
@@ -51,7 +51,7 @@
/** @hide */
public AppSearchSchema(@NonNull Bundle bundle) {
- Preconditions.checkNotNull(bundle);
+ Objects.requireNonNull(bundle);
mBundle = bundle;
}
@@ -125,7 +125,7 @@
/** Creates a new {@link AppSearchSchema.Builder}. */
public Builder(@NonNull String schemaType) {
- Preconditions.checkNotNull(schemaType);
+ Objects.requireNonNull(schemaType);
mSchemaType = schemaType;
}
@@ -133,7 +133,7 @@
@NonNull
public AppSearchSchema.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(propertyConfig);
+ Objects.requireNonNull(propertyConfig);
String name = propertyConfig.getName();
if (!mPropertyNames.add(name)) {
throw new IllegalSchemaException("Property defined more than once: " + name);
@@ -246,7 +246,7 @@
@Nullable private Integer mHashCode;
PropertyConfig(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
}
@Override
@@ -712,7 +712,7 @@
/** Returns the logical schema-type of the contents of this document property. */
@NonNull
public String getSchemaType() {
- return Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
+ return Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD));
}
/**
@@ -755,7 +755,7 @@
@NonNull
public DocumentPropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemaType);
+ Objects.requireNonNull(schemaType);
mBundle.putString(SCHEMA_TYPE_FIELD, schemaType);
return this;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
index 8c9d950..e3b3a85 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GenericDocument.java
@@ -31,6 +31,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Objects;
import java.util.Set;
/**
@@ -101,11 +102,11 @@
* @hide
*/
public GenericDocument(@NonNull Bundle bundle) {
- Preconditions.checkNotNull(bundle);
+ Objects.requireNonNull(bundle);
mBundle = bundle;
- mProperties = Preconditions.checkNotNull(bundle.getParcelable(PROPERTIES_FIELD));
- mUri = Preconditions.checkNotNull(mBundle.getString(URI_FIELD));
- mSchemaType = Preconditions.checkNotNull(mBundle.getString(SCHEMA_TYPE_FIELD));
+ mProperties = Objects.requireNonNull(bundle.getParcelable(PROPERTIES_FIELD));
+ mUri = Objects.requireNonNull(mBundle.getString(URI_FIELD));
+ mSchemaType = Objects.requireNonNull(mBundle.getString(SCHEMA_TYPE_FIELD));
mCreationTimestampMillis =
mBundle.getLong(CREATION_TIMESTAMP_MILLIS_FIELD, System.currentTimeMillis());
}
@@ -199,7 +200,7 @@
*/
@Nullable
public Object getProperty(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
Object property = mProperties.get(key);
if (property instanceof ArrayList) {
return getPropertyBytesArray(key);
@@ -218,7 +219,7 @@
*/
@Nullable
public String getPropertyString(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
String[] propertyArray = getPropertyStringArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return null;
@@ -235,7 +236,7 @@
* there is no such key or the value is of a different type.
*/
public long getPropertyLong(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
long[] propertyArray = getPropertyLongArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return 0;
@@ -252,7 +253,7 @@
* if there is no such key or the value is of a different type.
*/
public double getPropertyDouble(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
double[] propertyArray = getPropertyDoubleArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return 0.0;
@@ -269,7 +270,7 @@
* false} if there is no such key or the value is of a different type.
*/
public boolean getPropertyBoolean(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
boolean[] propertyArray = getPropertyBooleanArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return false;
@@ -287,7 +288,7 @@
*/
@Nullable
public byte[] getPropertyBytes(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
byte[][] propertyArray = getPropertyBytesArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return null;
@@ -305,7 +306,7 @@
*/
@Nullable
public GenericDocument getPropertyDocument(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
GenericDocument[] propertyArray = getPropertyDocumentArray(key);
if (propertyArray == null || propertyArray.length == 0) {
return null;
@@ -342,7 +343,7 @@
*/
@Nullable
public String[] getPropertyStringArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return getAndCastPropertyArray(key, String[].class);
}
@@ -355,7 +356,7 @@
*/
@Nullable
public long[] getPropertyLongArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return getAndCastPropertyArray(key, long[].class);
}
@@ -368,7 +369,7 @@
*/
@Nullable
public double[] getPropertyDoubleArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return getAndCastPropertyArray(key, double[].class);
}
@@ -381,7 +382,7 @@
*/
@Nullable
public boolean[] getPropertyBooleanArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
return getAndCastPropertyArray(key, boolean[].class);
}
@@ -396,7 +397,7 @@
@Nullable
@SuppressWarnings("unchecked")
public byte[][] getPropertyBytesArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
ArrayList<Bundle> bundles = getAndCastPropertyArray(key, ArrayList.class);
if (bundles == null || bundles.size() == 0) {
return null;
@@ -428,7 +429,7 @@
@SuppressLint("ArrayReturn")
@Nullable
public GenericDocument[] getPropertyDocumentArray(@NonNull String key) {
- Preconditions.checkNotNull(key);
+ Objects.requireNonNull(key);
Parcelable[] bundles = getAndCastPropertyArray(key, Parcelable[].class);
if (bundles == null || bundles.length == 0) {
return null;
@@ -591,9 +592,9 @@
*/
@SuppressWarnings("unchecked")
public Builder(@NonNull String namespace, @NonNull String uri, @NonNull String schemaType) {
- Preconditions.checkNotNull(namespace);
- Preconditions.checkNotNull(uri);
- Preconditions.checkNotNull(schemaType);
+ Objects.requireNonNull(namespace);
+ Objects.requireNonNull(uri);
+ Objects.requireNonNull(schemaType);
mBuilderTypeInstance = (BuilderType) this;
mBundle.putString(GenericDocument.NAMESPACE_FIELD, namespace);
mBundle.putString(GenericDocument.URI_FIELD, uri);
@@ -682,8 +683,8 @@
@NonNull
public BuilderType setPropertyString(@NonNull String key, @NonNull String... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -694,15 +695,14 @@
*
* @param key the key associated with the {@code values}.
* @param values the {@code boolean} values of the property.
- * @throws IllegalArgumentException if no values are provided or if values exceed maximum
- * repeated property length.
+ * @throws IllegalArgumentException if values exceed maximum repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyBoolean(@NonNull String key, @NonNull boolean... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -712,15 +712,14 @@
*
* @param key the key associated with the {@code values}.
* @param values the {@code long} values of the property.
- * @throws IllegalArgumentException if no values are provided or if values exceed maximum
- * repeated property length.
+ * @throws IllegalArgumentException if values exceed maximum repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyLong(@NonNull String key, @NonNull long... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -730,15 +729,14 @@
*
* @param key the key associated with the {@code values}.
* @param values the {@code double} values of the property.
- * @throws IllegalArgumentException if no values are provided or if values exceed maximum
- * repeated property length.
+ * @throws IllegalArgumentException if values exceed maximum repeated property length.
* @throws IllegalStateException if the builder has already been used.
*/
@NonNull
public BuilderType setPropertyDouble(@NonNull String key, @NonNull double... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -755,8 +753,8 @@
@NonNull
public BuilderType setPropertyBytes(@NonNull String key, @NonNull byte[]... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -776,8 +774,8 @@
public BuilderType setPropertyDocument(
@NonNull String key, @NonNull GenericDocument... values) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(key);
- Preconditions.checkNotNull(values);
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
putInPropertyBundle(key, values);
return mBuilderTypeInstance;
}
@@ -850,9 +848,7 @@
}
private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
- if (length == 0) {
- throw new IllegalArgumentException("The input array is empty.");
- } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
+ if (length > MAX_REPEATED_PROPERTY_LENGTH) {
throw new IllegalArgumentException(
"Repeated property \""
+ key
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
index 1719e14..4dc3225 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -52,9 +53,9 @@
@NonNull String namespace,
@NonNull Set<String> uris,
@NonNull Map<String, List<String>> typePropertyPathsMap) {
- mNamespace = Preconditions.checkNotNull(namespace);
- mUris = Preconditions.checkNotNull(uris);
- mTypePropertyPathsMap = Preconditions.checkNotNull(typePropertyPathsMap);
+ mNamespace = Objects.requireNonNull(namespace);
+ mUris = Objects.requireNonNull(uris);
+ mTypePropertyPathsMap = Objects.requireNonNull(typePropertyPathsMap);
}
/** Returns the namespace attached to the request. */
@@ -114,7 +115,7 @@
/** Creates a {@link GetByUriRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
- mNamespace = Preconditions.checkNotNull(namespace);
+ mNamespace = Objects.requireNonNull(namespace);
}
/**
@@ -124,7 +125,7 @@
*/
@NonNull
public Builder addUris(@NonNull String... uris) {
- Preconditions.checkNotNull(uris);
+ Objects.requireNonNull(uris);
return addUris(Arrays.asList(uris));
}
@@ -136,7 +137,7 @@
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(uris);
+ Objects.requireNonNull(uris);
mUris.addAll(uris);
return this;
}
@@ -161,11 +162,11 @@
public Builder addProjection(
@NonNull String schemaType, @NonNull Collection<String> propertyPaths) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemaType);
- Preconditions.checkNotNull(propertyPaths);
+ Objects.requireNonNull(schemaType);
+ Objects.requireNonNull(propertyPaths);
List<String> propertyPathsList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) {
- Preconditions.checkNotNull(propertyPath);
+ Objects.requireNonNull(propertyPath);
propertyPathsList.add(propertyPath);
}
mProjectionTypePropertyPaths.put(schemaType, propertyPathsList);
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
index 1f56ef3..691ef4f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetSchemaResponse.java
@@ -24,6 +24,7 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Objects;
import java.util.Set;
/** The response class of {@link AppSearchSession#getSchema} */
@@ -34,7 +35,7 @@
private final Bundle mBundle;
GetSchemaResponse(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
}
/**
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
index bfb9323..4f63bae 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PackageIdentifier.java
@@ -20,7 +20,7 @@
import android.app.appsearch.util.BundleUtil;
import android.os.Bundle;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/** This class represents a uniquely identifiable package. */
public class PackageIdentifier {
@@ -43,7 +43,7 @@
/** @hide */
public PackageIdentifier(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
}
/** @hide */
@@ -54,12 +54,12 @@
@NonNull
public String getPackageName() {
- return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
+ return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
}
@NonNull
public byte[] getSha256Certificate() {
- return Preconditions.checkNotNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
+ return Objects.requireNonNull(mBundle.getByteArray(SHA256_CERTIFICATE_FIELD));
}
@Override
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
index 01473be..b49e0e8 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/PutDocumentsRequest.java
@@ -26,6 +26,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* Encapsulates a request to index documents into an {@link AppSearchSession} database.
@@ -61,7 +62,7 @@
*/
@NonNull
public Builder addGenericDocuments(@NonNull GenericDocument... documents) {
- Preconditions.checkNotNull(documents);
+ Objects.requireNonNull(documents);
return addGenericDocuments(Arrays.asList(documents));
}
@@ -74,7 +75,7 @@
public Builder addGenericDocuments(
@NonNull Collection<? extends GenericDocument> documents) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(documents);
+ Objects.requireNonNull(documents);
mDocuments.addAll(documents);
return this;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
index 8da68c0..4dcad68 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java
@@ -24,6 +24,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Objects;
import java.util.Set;
/**
@@ -65,7 +66,7 @@
/** Creates a {@link RemoveByUriRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
- mNamespace = Preconditions.checkNotNull(namespace);
+ mNamespace = Objects.requireNonNull(namespace);
}
/**
@@ -75,7 +76,7 @@
*/
@NonNull
public Builder addUris(@NonNull String... uris) {
- Preconditions.checkNotNull(uris);
+ Objects.requireNonNull(uris);
return addUris(Arrays.asList(uris));
}
@@ -87,7 +88,7 @@
@NonNull
public Builder addUris(@NonNull Collection<String> uris) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(uris);
+ Objects.requireNonNull(uris);
mUris.addAll(uris);
return this;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
index 2e152f8..8aff3b4 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportSystemUsageRequest.java
@@ -20,6 +20,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/**
* A request to report usage of a document owned by another app from a system UI surface.
*
@@ -42,10 +44,10 @@
@NonNull String namespace,
@NonNull String uri,
long usageTimeMillis) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mDatabase = Preconditions.checkNotNull(database);
- mNamespace = Preconditions.checkNotNull(namespace);
- mUri = Preconditions.checkNotNull(uri);
+ mPackageName = Objects.requireNonNull(packageName);
+ mDatabase = Objects.requireNonNull(database);
+ mNamespace = Objects.requireNonNull(namespace);
+ mUri = Objects.requireNonNull(uri);
mUsageTimeMillis = usageTimeMillis;
}
@@ -95,9 +97,9 @@
/** 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);
+ mPackageName = Objects.requireNonNull(packageName);
+ mDatabase = Objects.requireNonNull(database);
+ mNamespace = Objects.requireNonNull(namespace);
}
/**
@@ -110,7 +112,7 @@
@NonNull
public ReportSystemUsageRequest.Builder setUri(@NonNull String uri) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(uri);
+ Objects.requireNonNull(uri);
mUri = uri;
return this;
}
@@ -142,7 +144,7 @@
@NonNull
public ReportSystemUsageRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI");
+ Objects.requireNonNull(mUri, "ReportUsageRequest is missing a URI");
if (mUsageTimeMillis == null) {
mUsageTimeMillis = System.currentTimeMillis();
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
index 646e73c..925bde9 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/ReportUsageRequest.java
@@ -20,6 +20,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/**
* A request to report usage of a document.
*
@@ -33,8 +35,8 @@
private final long mUsageTimeMillis;
ReportUsageRequest(@NonNull String namespace, @NonNull String uri, long usageTimeMillis) {
- mNamespace = Preconditions.checkNotNull(namespace);
- mUri = Preconditions.checkNotNull(uri);
+ mNamespace = Objects.requireNonNull(namespace);
+ mUri = Objects.requireNonNull(uri);
mUsageTimeMillis = usageTimeMillis;
}
@@ -69,7 +71,7 @@
/** Creates a {@link ReportUsageRequest.Builder} instance. */
public Builder(@NonNull String namespace) {
- mNamespace = Preconditions.checkNotNull(namespace);
+ mNamespace = Objects.requireNonNull(namespace);
}
/**
@@ -82,7 +84,7 @@
@NonNull
public ReportUsageRequest.Builder setUri(@NonNull String uri) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(uri);
+ Objects.requireNonNull(uri);
mUri = uri;
return this;
}
@@ -114,7 +116,7 @@
@NonNull
public ReportUsageRequest build() {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(mUri, "ReportUsageRequest is missing a URI");
+ Objects.requireNonNull(mUri, "ReportUsageRequest is missing a URI");
if (mUsageTimeMillis == null) {
mUsageTimeMillis = System.currentTimeMillis();
}
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 55a228d..432f838 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResult.java
@@ -59,7 +59,7 @@
/** @hide */
public SearchResult(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
}
/** @hide */
@@ -77,8 +77,7 @@
public GenericDocument getGenericDocument() {
if (mDocument == null) {
mDocument =
- new GenericDocument(
- Preconditions.checkNotNull(mBundle.getBundle(DOCUMENT_FIELD)));
+ new GenericDocument(Objects.requireNonNull(mBundle.getBundle(DOCUMENT_FIELD)));
}
return mDocument;
}
@@ -95,7 +94,7 @@
public List<MatchInfo> getMatches() {
if (mMatches == null) {
List<Bundle> matchBundles =
- Preconditions.checkNotNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
+ Objects.requireNonNull(mBundle.getParcelableArrayList(MATCHES_FIELD));
mMatches = new ArrayList<>(matchBundles.size());
for (int i = 0; i < matchBundles.size(); i++) {
MatchInfo matchInfo = new MatchInfo(matchBundles.get(i), getGenericDocument());
@@ -112,7 +111,7 @@
*/
@NonNull
public String getPackageName() {
- return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD));
+ return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD));
}
/**
@@ -122,7 +121,7 @@
*/
@NonNull
public String getDatabaseName() {
- return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD));
+ return Objects.requireNonNull(mBundle.getString(DATABASE_NAME_FIELD));
}
/**
@@ -169,8 +168,8 @@
* @param databaseName the database name the matched document belongs to.
*/
public Builder(@NonNull String packageName, @NonNull String databaseName) {
- mBundle.putString(PACKAGE_NAME_FIELD, Preconditions.checkNotNull(packageName));
- mBundle.putString(DATABASE_NAME_FIELD, Preconditions.checkNotNull(databaseName));
+ mBundle.putString(PACKAGE_NAME_FIELD, Objects.requireNonNull(packageName));
+ mBundle.putString(DATABASE_NAME_FIELD, Objects.requireNonNull(databaseName));
}
/**
@@ -312,9 +311,9 @@
@Nullable private MatchRange mWindowRange;
MatchInfo(@NonNull Bundle bundle, @Nullable GenericDocument document) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
mDocument = document;
- mPropertyPath = Preconditions.checkNotNull(bundle.getString(PROPERTY_PATH_FIELD));
+ mPropertyPath = Objects.requireNonNull(bundle.getString(PROPERTY_PATH_FIELD));
}
/**
@@ -449,7 +448,7 @@
Preconditions.checkState(!mBuilt, "Builder has already been used");
mBundle.putString(
SearchResult.MatchInfo.PROPERTY_PATH_FIELD,
- Preconditions.checkNotNull(propertyPath));
+ Objects.requireNonNull(propertyPath));
return this;
}
@@ -461,7 +460,7 @@
@NonNull
public Builder setExactMatchRange(@NonNull MatchRange matchRange) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(matchRange);
+ Objects.requireNonNull(matchRange);
mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_LOWER_FIELD, matchRange.getStart());
mBundle.putInt(MatchInfo.EXACT_MATCH_RANGE_UPPER_FIELD, matchRange.getEnd());
return this;
@@ -475,7 +474,7 @@
@NonNull
public Builder setSnippetRange(@NonNull MatchRange matchRange) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(matchRange);
+ Objects.requireNonNull(matchRange);
mBundle.putInt(MatchInfo.SNIPPET_RANGE_LOWER_FIELD, matchRange.getStart());
mBundle.putInt(MatchInfo.SNIPPET_RANGE_UPPER_FIELD, matchRange.getEnd());
return this;
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
index dbd09d6..4853b5b 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchResultPage.java
@@ -20,11 +20,10 @@
import android.annotation.Nullable;
import android.os.Bundle;
-import com.android.internal.util.Preconditions;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
/**
* This class represents a page of {@link SearchResult}s
@@ -41,7 +40,7 @@
@NonNull private final Bundle mBundle;
public SearchResultPage(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
mNextPageToken = mBundle.getLong(NEXT_PAGE_TOKEN_FIELD);
}
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 19d9430..d466bf1 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SearchSpec.java
@@ -34,6 +34,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -176,7 +177,7 @@
/** @hide */
public SearchSpec(@NonNull Bundle bundle) {
- Preconditions.checkNotNull(bundle);
+ Objects.requireNonNull(bundle);
mBundle = bundle;
}
@@ -342,7 +343,7 @@
*/
@NonNull
public Builder addFilterSchemas(@NonNull String... schemas) {
- Preconditions.checkNotNull(schemas);
+ Objects.requireNonNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used");
return addFilterSchemas(Arrays.asList(schemas));
}
@@ -355,7 +356,7 @@
*/
@NonNull
public Builder addFilterSchemas(@NonNull Collection<String> schemas) {
- Preconditions.checkNotNull(schemas);
+ Objects.requireNonNull(schemas);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mSchemas.addAll(schemas);
return this;
@@ -369,7 +370,7 @@
*/
@NonNull
public Builder addFilterNamespaces(@NonNull String... namespaces) {
- Preconditions.checkNotNull(namespaces);
+ Objects.requireNonNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used");
return addFilterNamespaces(Arrays.asList(namespaces));
}
@@ -382,7 +383,7 @@
*/
@NonNull
public Builder addFilterNamespaces(@NonNull Collection<String> namespaces) {
- Preconditions.checkNotNull(namespaces);
+ Objects.requireNonNull(namespaces);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mNamespaces.addAll(namespaces);
return this;
@@ -398,7 +399,7 @@
*/
@NonNull
public Builder addFilterPackageNames(@NonNull String... packageNames) {
- Preconditions.checkNotNull(packageNames);
+ Objects.requireNonNull(packageNames);
Preconditions.checkState(!mBuilt, "Builder has already been used");
return addFilterPackageNames(Arrays.asList(packageNames));
}
@@ -413,7 +414,7 @@
*/
@NonNull
public Builder addFilterPackageNames(@NonNull Collection<String> packageNames) {
- Preconditions.checkNotNull(packageNames);
+ Objects.requireNonNull(packageNames);
Preconditions.checkState(!mBuilt, "Builder has already been used");
mPackageNames.addAll(packageNames);
return this;
@@ -586,11 +587,11 @@
public SearchSpec.Builder addProjection(
@NonNull String schema, @NonNull Collection<String> propertyPaths) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schema);
- Preconditions.checkNotNull(propertyPaths);
+ Objects.requireNonNull(schema);
+ Objects.requireNonNull(propertyPaths);
ArrayList<String> propertyPathsArrayList = new ArrayList<>(propertyPaths.size());
for (String propertyPath : propertyPaths) {
- Preconditions.checkNotNull(propertyPath);
+ Objects.requireNonNull(propertyPath);
propertyPathsArrayList.add(propertyPath);
}
mProjectionTypePropertyMasks.putStringArrayList(schema, propertyPathsArrayList);
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 5672bc7..8f7a0bf 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaRequest.java
@@ -28,6 +28,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -94,10 +95,10 @@
@NonNull Map<String, Migrator> migrators,
boolean forceOverride,
int version) {
- mSchemas = Preconditions.checkNotNull(schemas);
- mSchemasNotDisplayedBySystem = Preconditions.checkNotNull(schemasNotDisplayedBySystem);
- mSchemasVisibleToPackages = Preconditions.checkNotNull(schemasVisibleToPackages);
- mMigrators = Preconditions.checkNotNull(migrators);
+ mSchemas = Objects.requireNonNull(schemas);
+ mSchemasNotDisplayedBySystem = Objects.requireNonNull(schemasNotDisplayedBySystem);
+ mSchemasVisibleToPackages = Objects.requireNonNull(schemasVisibleToPackages);
+ mMigrators = Objects.requireNonNull(migrators);
mForceOverride = forceOverride;
mVersion = version;
}
@@ -192,7 +193,7 @@
*/
@NonNull
public Builder addSchemas(@NonNull AppSearchSchema... schemas) {
- Preconditions.checkNotNull(schemas);
+ Objects.requireNonNull(schemas);
return addSchemas(Arrays.asList(schemas));
}
@@ -206,7 +207,7 @@
@NonNull
public Builder addSchemas(@NonNull Collection<AppSearchSchema> schemas) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- Preconditions.checkNotNull(schemas);
+ Objects.requireNonNull(schemas);
mSchemas.addAll(schemas);
return this;
}
@@ -231,7 +232,7 @@
@NonNull
public Builder setSchemaTypeDisplayedBySystem(
@NonNull String schemaType, boolean displayed) {
- Preconditions.checkNotNull(schemaType);
+ Objects.requireNonNull(schemaType);
Preconditions.checkState(!mBuilt, "Builder has already been used");
if (displayed) {
@@ -270,8 +271,8 @@
@NonNull String schemaType,
boolean visible,
@NonNull PackageIdentifier packageIdentifier) {
- Preconditions.checkNotNull(schemaType);
- Preconditions.checkNotNull(packageIdentifier);
+ Objects.requireNonNull(schemaType);
+ Objects.requireNonNull(packageIdentifier);
Preconditions.checkState(!mBuilt, "Builder has already been used");
Set<PackageIdentifier> packageIdentifiers = mSchemasVisibleToPackages.get(schemaType);
@@ -321,8 +322,8 @@
@NonNull
@SuppressLint("MissingGetterMatchingBuilder") // Getter return plural objects.
public Builder setMigrator(@NonNull String schemaType, @NonNull Migrator migrator) {
- Preconditions.checkNotNull(schemaType);
- Preconditions.checkNotNull(migrator);
+ Objects.requireNonNull(schemaType);
+ Objects.requireNonNull(migrator);
mMigrators.put(schemaType, migrator);
return this;
}
@@ -350,7 +351,7 @@
*/
@NonNull
public Builder setMigrators(@NonNull Map<String, Migrator> migrators) {
- Preconditions.checkNotNull(migrators);
+ Objects.requireNonNull(migrators);
mMigrators.putAll(migrators);
return this;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
index d63e437..7be589f 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java
@@ -27,6 +27,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/** The response class of {@link AppSearchSession#setSchema} */
@@ -61,8 +62,8 @@
@Nullable private Set<String> mIncompatibleTypes;
SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
- mBundle = Preconditions.checkNotNull(bundle);
- mMigrationFailures = Preconditions.checkNotNull(migrationFailures);
+ mBundle = Objects.requireNonNull(bundle);
+ mMigrationFailures = Objects.requireNonNull(migrationFailures);
}
SetSchemaResponse(@NonNull Bundle bundle) {
@@ -103,7 +104,7 @@
if (mDeletedTypes == null) {
mDeletedTypes =
new ArraySet<>(
- Preconditions.checkNotNull(
+ Objects.requireNonNull(
mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mDeletedTypes);
@@ -118,7 +119,7 @@
if (mMigratedTypes == null) {
mMigratedTypes =
new ArraySet<>(
- Preconditions.checkNotNull(
+ Objects.requireNonNull(
mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mMigratedTypes);
@@ -139,7 +140,7 @@
if (mIncompatibleTypes == null) {
mIncompatibleTypes =
new ArraySet<>(
- Preconditions.checkNotNull(
+ Objects.requireNonNull(
mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
}
return Collections.unmodifiableSet(mIncompatibleTypes);
@@ -173,7 +174,7 @@
public Builder addMigrationFailures(
@NonNull Collection<MigrationFailure> migrationFailures) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mMigrationFailures.addAll(Preconditions.checkNotNull(migrationFailures));
+ mMigrationFailures.addAll(Objects.requireNonNull(migrationFailures));
return this;
}
@@ -181,7 +182,7 @@
@NonNull
public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mMigrationFailures.add(Preconditions.checkNotNull(migrationFailure));
+ mMigrationFailures.add(Objects.requireNonNull(migrationFailure));
return this;
}
@@ -189,7 +190,7 @@
@NonNull
public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes));
+ mDeletedTypes.addAll(Objects.requireNonNull(deletedTypes));
return this;
}
@@ -197,7 +198,7 @@
@NonNull
public Builder addDeletedType(@NonNull String deletedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mDeletedTypes.add(Preconditions.checkNotNull(deletedType));
+ mDeletedTypes.add(Objects.requireNonNull(deletedType));
return this;
}
@@ -205,7 +206,7 @@
@NonNull
public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes));
+ mIncompatibleTypes.addAll(Objects.requireNonNull(incompatibleTypes));
return this;
}
@@ -213,7 +214,7 @@
@NonNull
public Builder addIncompatibleType(@NonNull String incompatibleType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mIncompatibleTypes.add(Preconditions.checkNotNull(incompatibleType));
+ mIncompatibleTypes.add(Objects.requireNonNull(incompatibleType));
return this;
}
@@ -221,7 +222,7 @@
@NonNull
public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes));
+ mMigratedTypes.addAll(Objects.requireNonNull(migratedTypes));
return this;
}
@@ -229,7 +230,7 @@
@NonNull
public Builder addMigratedType(@NonNull String migratedType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mMigratedTypes.add(Preconditions.checkNotNull(migratedType));
+ mMigratedTypes.add(Objects.requireNonNull(migratedType));
return this;
}
@@ -318,7 +319,7 @@
@NonNull
public Builder setSchemaType(@NonNull String schemaType) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mSchemaType = Preconditions.checkNotNull(schemaType);
+ mSchemaType = Objects.requireNonNull(schemaType);
return this;
}
@@ -326,7 +327,7 @@
@NonNull
public Builder setNamespace(@NonNull String namespace) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mNamespace = Preconditions.checkNotNull(namespace);
+ mNamespace = Objects.requireNonNull(namespace);
return this;
}
@@ -334,7 +335,7 @@
@NonNull
public Builder setUri(@NonNull String uri) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
- mUri = Preconditions.checkNotNull(uri);
+ mUri = Objects.requireNonNull(uri);
return this;
}
@@ -343,7 +344,7 @@
public Builder setAppSearchResult(@NonNull AppSearchResult<Void> appSearchResult) {
Preconditions.checkState(!mBuilt, "Builder has already been used");
Preconditions.checkState(!appSearchResult.isSuccess(), "Input a success result");
- mFailureResult = Preconditions.checkNotNull(appSearchResult);
+ mFailureResult = Objects.requireNonNull(appSearchResult);
return this;
}
diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
index dc04cf3..502b939 100644
--- a/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
+++ b/apex/appsearch/framework/java/external/android/app/appsearch/StorageInfo.java
@@ -21,6 +21,8 @@
import com.android.internal.util.Preconditions;
+import java.util.Objects;
+
/** The response class of {@code AppSearchSession#getStorageInfo}. */
public class StorageInfo {
@@ -31,7 +33,7 @@
private final Bundle mBundle;
StorageInfo(@NonNull Bundle bundle) {
- mBundle = Preconditions.checkNotNull(bundle);
+ mBundle = Objects.requireNonNull(bundle);
}
/**
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 32d7e043..10e014b 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
@@ -37,7 +37,11 @@
public final class SchemaMigrationUtil {
private SchemaMigrationUtil() {}
- /** Returns all active {@link Migrator}s that need to be triggered in this migration. */
+ /**
+ * Returns all active {@link Migrator}s that need to be triggered in this migration.
+ *
+ * <p>{@link Migrator#shouldMigrate} returns {@code true} will make the {@link Migrator} active.
+ */
@NonNull
public static Map<String, Migrator> getActiveMigrators(
@NonNull Set<AppSearchSchema> existingSchemas,
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 509877e..f6f5c98 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -16,7 +16,6 @@
package com.android.server.appsearch;
import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
-import static android.os.Process.INVALID_UID;
import static android.os.UserHandle.USER_NULL;
import android.annotation.ElapsedRealtimeLong;
@@ -37,7 +36,6 @@
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaResponse;
import android.app.appsearch.StorageInfo;
-import android.app.appsearch.exceptions.AppSearchException;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -46,7 +44,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
-import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -54,9 +51,9 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
@@ -64,6 +61,8 @@
import com.android.server.appsearch.stats.LoggerInstanceManager;
import com.android.server.appsearch.stats.PlatformLogger;
+import com.google.android.icing.proto.PersistType;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
@@ -75,7 +74,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -94,7 +93,7 @@
// mutate requests will need to gain write lock and query requests need to gain read lock.
private static final Executor EXECUTOR = new ThreadPoolExecutor(/*corePoolSize=*/1,
Runtime.getRuntime().availableProcessors(), /*keepAliveTime*/ 60L, TimeUnit.SECONDS,
- new SynchronousQueue<Runnable>());
+ new LinkedBlockingQueue<>());
// Cache of unlocked user ids so we don't have to query UserManager service each time. The
// "locked" suffix refers to the fact that access to the field should be locked; unrelated to
@@ -121,14 +120,6 @@
mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
new IntentFilter(Intent.ACTION_USER_REMOVED), /*broadcastPermission=*/ null,
/*scheduler=*/ null);
-
- IntentFilter packageChangedFilter = new IntentFilter();
- packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
- packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
- packageChangedFilter.addDataScheme("package");
- mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
- packageChangedFilter, /*broadcastPermission=*/ null,
- /*scheduler=*/ null);
}
private class UserActionReceiver extends BroadcastReceiver {
@@ -136,15 +127,15 @@
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_USER_REMOVED:
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
if (userId == USER_NULL) {
- Log.e(TAG, "userId is missing in the intent: " + intent);
+ Slog.e(TAG, "userId is missing in the intent: " + intent);
return;
}
handleUserRemoved(userId);
break;
default:
- Log.e(TAG, "Received unknown intent: " + intent);
+ Slog.e(TAG, "Received unknown intent: " + intent);
}
}
}
@@ -164,44 +155,9 @@
try {
mImplInstanceManager.removeAppSearchImplForUser(userId);
mLoggerInstanceManager.removePlatformLoggerForUser(userId);
- Log.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
+ Slog.i(TAG, "Removed AppSearchImpl instance for user: " + userId);
} catch (Throwable t) {
- Log.e(TAG, "Unable to remove data for user: " + userId, t);
- }
- }
-
- private class PackageChangedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(@NonNull Context context, @NonNull Intent intent) {
- switch (intent.getAction()) {
- case Intent.ACTION_PACKAGE_FULLY_REMOVED:
- case Intent.ACTION_PACKAGE_DATA_CLEARED:
- String packageName = intent.getData().getSchemeSpecificPart();
- if (packageName == null) {
- Log.e(TAG, "Package name is missing in the intent: " + intent);
- return;
- }
- int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
- if (uid == INVALID_UID) {
- Log.e(TAG, "uid is missing in the intent: " + intent);
- return;
- }
- handlePackageRemoved(packageName, uid);
- break;
- default:
- Log.e(TAG, "Received unknown intent: " + intent);
- }
- }
- }
-
- private void handlePackageRemoved(String packageName, int uid) {
- int userId = UserHandle.getUserId(uid);
- try {
- AppSearchImpl impl = mImplInstanceManager.getOrCreateAppSearchImpl(mContext, userId);
- //TODO(b/145759910) clear visibility setting for package.
- impl.clearPackageData(packageName);
- } catch (AppSearchException e) {
- Log.e(TAG, "Unable to remove data for package: " + packageName, e);
+ Slog.e(TAG, "Unable to remove data for user: " + userId, t);
}
}
@@ -224,10 +180,10 @@
int schemaVersion,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(schemaBundles);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(schemaBundles);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -273,9 +229,9 @@
@NonNull String databaseName,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -300,9 +256,9 @@
@NonNull String databaseName,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -328,10 +284,10 @@
@UserIdInt int userId,
@ElapsedRealtimeLong long binderCallStartTimeMillis,
@NonNull IAppSearchBatchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(documentBundles);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(documentBundles);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -364,6 +320,8 @@
++operationFailureCount;
}
}
+ // Now that the batch has been written. Persist the newly written data.
+ impl.persistToDisk(PersistType.Code.LITE);
invokeCallbackOnResult(callback, resultBuilder.build());
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
@@ -400,11 +358,11 @@
@NonNull Map<String, List<String>> typePropertyPaths,
@UserIdInt int userId,
@NonNull IAppSearchBatchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(namespace);
- Preconditions.checkNotNull(uris);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(namespace);
+ Objects.requireNonNull(uris);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -445,11 +403,11 @@
@NonNull Bundle searchSpecBundle,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(queryExpression);
- Preconditions.checkNotNull(searchSpecBundle);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(queryExpression);
+ Objects.requireNonNull(searchSpecBundle);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -480,10 +438,10 @@
@NonNull Bundle searchSpecBundle,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(queryExpression);
- Preconditions.checkNotNull(searchSpecBundle);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(queryExpression);
+ Objects.requireNonNull(searchSpecBundle);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -512,7 +470,7 @@
long nextPageToken,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
// TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally
@@ -631,7 +589,7 @@
}
}
}
- impl.persistToDisk();
+ impl.persistToDisk(PersistType.Code.FULL);
invokeCallbackOnResult(callback,
AppSearchResult.newSuccessfulResult(migrationFailureBundles));
} catch (Throwable t) {
@@ -685,10 +643,10 @@
@NonNull List<String> uris,
@UserIdInt int userId,
@NonNull IAppSearchBatchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(uris);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(uris);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -708,6 +666,8 @@
resultBuilder.setResult(uri, throwableToFailedResult(t));
}
}
+ // Now that the batch has been written. Persist the newly written data.
+ impl.persistToDisk(PersistType.Code.LITE);
invokeCallbackOnResult(callback, resultBuilder.build());
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
@@ -723,11 +683,11 @@
@NonNull Bundle searchSpecBundle,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(queryExpression);
- Preconditions.checkNotNull(searchSpecBundle);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(queryExpression);
+ Objects.requireNonNull(searchSpecBundle);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -741,6 +701,8 @@
databaseName,
queryExpression,
new SearchSpec(searchSpecBundle));
+ // Now that the batch has been written. Persist the newly written data.
+ impl.persistToDisk(PersistType.Code.LITE);
invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(null));
} catch (Throwable t) {
invokeCallbackOnError(callback, t);
@@ -754,9 +716,9 @@
@NonNull String databaseName,
@UserIdInt int userId,
@NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(databaseName);
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(databaseName);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -785,7 +747,7 @@
verifyUserUnlocked(callingUserId);
AppSearchImpl impl =
mImplInstanceManager.getAppSearchImpl(callingUserId);
- impl.persistToDisk();
+ impl.persistToDisk(PersistType.Code.FULL);
} catch (Throwable t) {
Log.e(TAG, "Unable to persist the data to disk", t);
}
@@ -794,7 +756,7 @@
@Override
public void initialize(@UserIdInt int userId, @NonNull IAppSearchResultCallback callback) {
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(callback);
int callingUid = Binder.getCallingUid();
int callingUserId = handleIncomingUser(userId, callingUid);
EXECUTOR.execute(() -> {
@@ -825,7 +787,7 @@
}
private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) {
- Preconditions.checkNotNull(callingPackage);
+ Objects.requireNonNull(callingPackage);
if (mPackageManagerInternal.getPackageUid(
callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid))
!= callingUid) {
@@ -873,13 +835,12 @@
/**
* Invokes the {@link IAppSearchBatchResultCallback} with an unexpected internal throwable.
*
- * <p>The throwable is converted to {@link ParcelableException}.
+ * <p>The throwable is converted to {@link AppSearchResult}.
*/
private void invokeCallbackOnError(
- IAppSearchBatchResultCallback callback, Throwable throwable) {
+ @NonNull IAppSearchBatchResultCallback callback, @NonNull Throwable throwable) {
try {
- //TODO(b/175067650) verify ParcelableException could propagate throwable correctly.
- callback.onSystemError(new ParcelableException(throwable));
+ callback.onSystemError(throwableToFailedResult(throwable));
} catch (RemoteException e) {
Log.e(TAG, "Unable to send error to the callback", e);
}
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 1ed26d6..4de52fb 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java
@@ -35,13 +35,14 @@
import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.util.Preconditions;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -153,7 +154,7 @@
* AppSearchImpl.
*/
static final String VISIBILITY_STORE_PREFIX =
- AppSearchImpl.createPrefix(PACKAGE_NAME, DATABASE_NAME);
+ PrefixUtil.createPrefix(PACKAGE_NAME, DATABASE_NAME);
/** Namespace of documents that contain visibility settings */
private static final String NAMESPACE = "";
@@ -332,9 +333,9 @@
@NonNull Set<String> schemasNotPlatformSurfaceable,
@NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible)
throws AppSearchException {
- Preconditions.checkNotNull(prefix);
- Preconditions.checkNotNull(schemasNotPlatformSurfaceable);
- Preconditions.checkNotNull(schemasPackageAccessible);
+ Objects.requireNonNull(prefix);
+ Objects.requireNonNull(schemasNotPlatformSurfaceable);
+ Objects.requireNonNull(schemasPackageAccessible);
// Persist the document
GenericDocument.Builder<?> visibilityDocument =
@@ -382,8 +383,8 @@
/** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */
public boolean isSchemaSearchableByCaller(
@NonNull String prefix, @NonNull String prefixedSchema, int callerUid) {
- Preconditions.checkNotNull(prefix);
- Preconditions.checkNotNull(prefixedSchema);
+ Objects.requireNonNull(prefix);
+ Objects.requireNonNull(prefixedSchema);
// We compare appIds here rather than direct uids because the package's uid may change based
// on the user that's running.
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 5f8cbee..50ac054 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
@@ -16,10 +16,18 @@
package com.android.server.appsearch.external.localstorage;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPackagePrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getDatabaseName;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
-import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.GetByUriRequest;
@@ -39,7 +47,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.ResultCodeToProtoConverter;
import com.android.server.appsearch.external.localstorage.converter.SchemaToProtoConverter;
@@ -66,7 +73,6 @@
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;
import com.google.android.icing.proto.ReportUsageResultProto;
import com.google.android.icing.proto.ResetResultProto;
@@ -89,6 +95,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -132,10 +139,6 @@
public final class AppSearchImpl implements Closeable {
private static final String TAG = "AppSearchImpl";
- @VisibleForTesting static final char DATABASE_DELIMITER = '/';
-
- @VisibleForTesting static final char PACKAGE_DELIMITER = '$';
-
@VisibleForTesting static final int OPTIMIZE_THRESHOLD_DOC_COUNT = 1000;
@VisibleForTesting static final int OPTIMIZE_THRESHOLD_BYTES = 1_000_000; // 1MB
@VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
@@ -148,11 +151,12 @@
@GuardedBy("mReadWriteLock")
private final VisibilityStore mVisibilityStoreLocked;
- // This map contains schemaTypes for all package-database prefixes. All values in the map are
- // prefixed with the package-database prefix.
- // TODO(b/172360376): Check if this can be replaced with an ArrayMap
+ // This map contains schema types and SchemaTypeConfigProtos for all package-database
+ // prefixes. It maps each package-database prefix to an inner-map. The inner-map maps each
+ // prefixed schema type to its respective SchemaTypeConfigProto.
@GuardedBy("mReadWriteLock")
- private final Map<String, Set<String>> mSchemaMapLocked = new HashMap<>();
+ private final Map<String, Map<String, SchemaTypeConfigProto>> mSchemaMapLocked =
+ new ArrayMap<>();
// This map contains namespaces for all package-database prefixes. All values in the map are
// prefixed with the package-database prefix.
@@ -182,9 +186,9 @@
int userId,
@NonNull String globalQuerierPackage)
throws AppSearchException {
- Preconditions.checkNotNull(icingDir);
- Preconditions.checkNotNull(context);
- Preconditions.checkNotNull(globalQuerierPackage);
+ Objects.requireNonNull(icingDir);
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(globalQuerierPackage);
AppSearchImpl appSearchImpl =
new AppSearchImpl(icingDir, context, userId, globalQuerierPackage);
appSearchImpl.initializeVisibilityStore();
@@ -229,7 +233,7 @@
// Populate schema map
for (SchemaTypeConfigProto schema : schemaProto.getTypesList()) {
String prefixedSchemaType = schema.getSchemaType();
- addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), prefixedSchemaType);
+ addToMap(mSchemaMapLocked, getPrefix(prefixedSchemaType), schema);
}
// Populate namespace map
@@ -278,7 +282,7 @@
return;
}
- persistToDisk();
+ persistToDisk(PersistType.Code.FULL);
mIcingSearchEngineLocked.close();
mClosedLocked = true;
} catch (AppSearchException e) {
@@ -364,7 +368,14 @@
}
// Update derived data structures.
- mSchemaMapLocked.put(prefix, rewrittenSchemaResults.mRewrittenPrefixedTypes);
+ for (SchemaTypeConfigProto schemaTypeConfigProto :
+ rewrittenSchemaResults.mRewrittenPrefixedTypes.values()) {
+ addToMap(mSchemaMapLocked, prefix, schemaTypeConfigProto);
+ }
+
+ for (String schemaType : rewrittenSchemaResults.mDeletedPrefixedTypes) {
+ removeFromMap(mSchemaMapLocked, prefix, schemaType);
+ }
Set<String> prefixedSchemasNotPlatformSurfaceable =
new ArraySet<>(schemasNotPlatformSurfaceable.size());
@@ -586,7 +597,7 @@
mReadWriteLock.readLock().lock();
try {
throwIfClosedLocked();
-
+ String prefix = createPrefix(packageName, databaseName);
List<TypePropertyMask> nonPrefixedPropertyMasks =
TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths);
List<TypePropertyMask> prefixedPropertyMasks =
@@ -597,7 +608,7 @@
String prefixedType =
nonPrefixedType.equals(GetByUriRequest.PROJECTION_SCHEMA_TYPE_WILDCARD)
? nonPrefixedType
- : createPrefix(packageName, databaseName) + nonPrefixedType;
+ : prefix + nonPrefixedType;
prefixedPropertyMasks.add(
typePropertyMask.toBuilder().setSchemaType(prefixedType).build());
}
@@ -607,15 +618,17 @@
.build();
GetResultProto getResultProto =
- mIcingSearchEngineLocked.get(
- createPrefix(packageName, databaseName) + namespace,
- uri,
- getResultSpec);
+ mIcingSearchEngineLocked.get(prefix + namespace, uri, getResultSpec);
checkSuccess(getResultProto.getStatus());
+ // The schema type map cannot be null at this point. It could only be null if no
+ // schema had ever been set for that prefix. Given we have retrieved a document from
+ // the index, we know a schema had to have been set.
+ Map<String, SchemaTypeConfigProto> schemaTypeMap = mSchemaMapLocked.get(prefix);
DocumentProto.Builder documentBuilder = getResultProto.getDocument().toBuilder();
removePrefixesFromDocument(documentBuilder);
- return GenericDocumentToProtoConverter.toGenericDocument(documentBuilder.build());
+ return GenericDocumentToProtoConverter.toGenericDocument(
+ documentBuilder.build(), prefix, schemaTypeMap);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -726,7 +739,7 @@
}
} else {
// Client didn't specify certain schemas to search over, check all schemas
- Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix);
+ Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix).keySet();
if (prefixedSchemas != null) {
for (String prefixedSchema : prefixedSchemas) {
if (packageName.equals(callerPackageName)
@@ -816,7 +829,7 @@
searchSpecBuilder.build(), scoringSpec, resultSpecBuilder.build());
checkSuccess(searchResultProto.getStatus());
- return rewriteSearchResultProto(searchResultProto);
+ return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
}
/**
@@ -838,7 +851,7 @@
SearchResultProto searchResultProto =
mIcingSearchEngineLocked.getNextPage(nextPageToken);
checkSuccess(searchResultProto.getStatus());
- return rewriteSearchResultProto(searchResultProto);
+ return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked);
} finally {
mReadWriteLock.readLock().unlock();
}
@@ -1104,22 +1117,31 @@
/**
* Persists all update/delete requests to the disk.
*
- * <p>If the app crashes after a call to PersistToDisk(), Icing would be able to fully recover
- * all data written up to this point without a costly recovery process.
+ * <p>If the app crashes after a call to PersistToDisk with {@link PersistType.Code#FULL}, Icing
+ * would be able to fully recover all data written up to this point without a costly recovery
+ * process.
*
- * <p>If the app crashes before a call to PersistToDisk(), Icing would trigger a costly recovery
- * process in next initialization. After that, Icing would still be able to recover all written
- * data.
+ * <p>If the app crashes after a call to PersistToDisk with {@link PersistType.Code#LITE}, Icing
+ * would trigger a costly recovery process in next initialization. After that, Icing would still
+ * be able to recover all written data - excepting Usage data. Usage data is only guaranteed to
+ * be safe after a call to PersistToDisk with {@link PersistType.Code#FULL}
*
+ * <p>If the app crashes after an update/delete request has been made, but before any call to
+ * PersistToDisk, then all data in Icing will be lost.
+ *
+ * @param persistType the amount of data to persist. {@link PersistType.Code#LITE} will only
+ * persist the minimal amount of data to ensure all data can be recovered. {@link
+ * PersistType.Code#FULL} will persist all data necessary to prevent data loss without
+ * needing data recovery.
* @throws AppSearchException on any error that AppSearch persist data to disk.
*/
- public void persistToDisk() throws AppSearchException {
+ public void persistToDisk(@NonNull PersistType.Code persistType) throws AppSearchException {
mReadWriteLock.writeLock().lock();
try {
throwIfClosedLocked();
PersistToDiskResultProto persistToDiskResultProto =
- mIcingSearchEngineLocked.persistToDisk(PersistType.Code.FULL);
+ mIcingSearchEngineLocked.persistToDisk(persistType);
checkSuccess(persistToDiskResultProto.getStatus());
} finally {
mReadWriteLock.writeLock().unlock();
@@ -1189,8 +1211,8 @@
// Any prefixed types that used to exist in the schema, but are deleted in the new one.
final Set<String> mDeletedPrefixedTypes = new ArraySet<>();
- // Prefixed types that were part of the new schema.
- final Set<String> mRewrittenPrefixedTypes = new ArraySet<>();
+ // Map of prefixed schema types to SchemaTypeConfigProtos that were part of the new schema.
+ final Map<String, SchemaTypeConfigProto> mRewrittenPrefixedTypes = new ArrayMap<>();
}
/**
@@ -1238,7 +1260,7 @@
// newTypesToProto is modified below, so we need a copy first
RewrittenSchemaResults rewrittenSchemaResults = new RewrittenSchemaResults();
- rewrittenSchemaResults.mRewrittenPrefixedTypes.addAll(newTypesToProto.keySet());
+ rewrittenSchemaResults.mRewrittenPrefixedTypes.putAll(newTypesToProto);
// Combine the existing schema (which may have types from other prefixes) with this
// prefix's new schema. Modifies the existingSchemaBuilder.
@@ -1264,99 +1286,6 @@
}
/**
- * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
- * documentBuilder}.
- *
- * @param documentBuilder The document to mutate
- * @param prefix The prefix to add
- */
- @VisibleForTesting
- static void addPrefixToDocument(
- @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
- // Rewrite the type name to include/remove the prefix.
- String newSchema = prefix + documentBuilder.getSchema();
- documentBuilder.setSchema(newSchema);
-
- // Rewrite the namespace to include/remove the prefix.
- documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
-
- // Recurse into derived documents
- for (int propertyIdx = 0;
- propertyIdx < documentBuilder.getPropertiesCount();
- propertyIdx++) {
- int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
- if (documentCount > 0) {
- PropertyProto.Builder propertyBuilder =
- documentBuilder.getProperties(propertyIdx).toBuilder();
- for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
- DocumentProto.Builder derivedDocumentBuilder =
- propertyBuilder.getDocumentValues(documentIdx).toBuilder();
- addPrefixToDocument(derivedDocumentBuilder, prefix);
- propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
- }
- documentBuilder.setProperties(propertyIdx, propertyBuilder);
- }
- }
- }
-
- /**
- * Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
- *
- * @param documentBuilder The document to mutate
- * @return Prefix name that was removed from the document.
- * @throws AppSearchException if there are unexpected database prefixing errors.
- */
- @NonNull
- @VisibleForTesting
- static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
- throws AppSearchException {
- // Rewrite the type name and namespace to remove the prefix.
- String schemaPrefix = getPrefix(documentBuilder.getSchema());
- String namespacePrefix = getPrefix(documentBuilder.getNamespace());
-
- if (!schemaPrefix.equals(namespacePrefix)) {
- throw new AppSearchException(
- AppSearchResult.RESULT_INTERNAL_ERROR,
- "Found unexpected"
- + " multiple prefix names in document: "
- + schemaPrefix
- + ", "
- + namespacePrefix);
- }
-
- documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
- documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
-
- // Recurse into derived documents
- for (int propertyIdx = 0;
- propertyIdx < documentBuilder.getPropertiesCount();
- propertyIdx++) {
- int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
- if (documentCount > 0) {
- PropertyProto.Builder propertyBuilder =
- documentBuilder.getProperties(propertyIdx).toBuilder();
- for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
- DocumentProto.Builder derivedDocumentBuilder =
- propertyBuilder.getDocumentValues(documentIdx).toBuilder();
- String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
- if (!nestedPrefix.equals(schemaPrefix)) {
- throw new AppSearchException(
- AppSearchResult.RESULT_INTERNAL_ERROR,
- "Found unexpected multiple prefix names in document: "
- + schemaPrefix
- + ", "
- + nestedPrefix);
- }
- propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
- }
- documentBuilder.setProperties(propertyIdx, propertyBuilder);
- }
- }
-
- return schemaPrefix;
- }
-
- /**
* Rewrites the search spec filters with {@code prefixes}.
*
* <p>This method should be only called in query methods and get the READ lock to keep thread
@@ -1443,9 +1372,9 @@
if (allowedPrefixedSchemas.isEmpty()) {
// If the client didn't specify any schema filters, search over all of their schemas
- Set<String> prefixedSchemas = mSchemaMapLocked.get(prefix);
- if (prefixedSchemas != null) {
- allowedPrefixedSchemas.addAll(prefixedSchemas);
+ Map<String, SchemaTypeConfigProto> prefixedSchemaMap = mSchemaMapLocked.get(prefix);
+ if (prefixedSchemaMap != null) {
+ allowedPrefixedSchemas.addAll(prefixedSchemaMap.keySet());
}
}
return allowedPrefixedSchemas;
@@ -1656,86 +1585,6 @@
return mSchemaMapLocked.keySet();
}
- @NonNull
- static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
- return createPackagePrefix(packageName) + databaseName + DATABASE_DELIMITER;
- }
-
- @NonNull
- private static String createPackagePrefix(@NonNull String packageName) {
- return packageName + PACKAGE_DELIMITER;
- }
-
- /**
- * Returns the package name that's contained within the {@code prefix}.
- *
- * @param prefix Prefix string that contains the package name inside of it. The package name
- * must be in the front of the string, and separated from the rest of the string by the
- * {@link #PACKAGE_DELIMITER}.
- * @return Valid package name.
- */
- @NonNull
- private static String getPackageName(@NonNull String prefix) {
- int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
- if (delimiterIndex == -1) {
- // This should never happen if we construct our prefixes properly
- Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
- return "";
- }
- return prefix.substring(0, delimiterIndex);
- }
-
- /**
- * Returns the database name that's contained within the {@code prefix}.
- *
- * @param prefix Prefix string that contains the database name inside of it. The database name
- * must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
- * @return Valid database name.
- */
- @NonNull
- private static String getDatabaseName(@NonNull String prefix) {
- int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
- int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
- if (packageDelimiterIndex == -1) {
- // This should never happen if we construct our prefixes properly
- Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
- return "";
- }
- if (databaseDelimiterIndex == -1) {
- // This should never happen if we construct our prefixes properly
- Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
- return "";
- }
- return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
- }
-
- @NonNull
- private static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
- // The prefix is made up of the package, then the database. So we only need to find the
- // database cutoff.
- int delimiterIndex;
- if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
- // Add 1 to include the char size of the DATABASE_DELIMITER
- return prefixedString.substring(delimiterIndex + 1);
- }
- throw new AppSearchException(
- AppSearchResult.RESULT_UNKNOWN_ERROR,
- "The prefixed value doesn't contains a valid database name.");
- }
-
- @NonNull
- private static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
- int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
- if (databaseDelimiterIndex == -1) {
- throw new AppSearchException(
- AppSearchResult.RESULT_UNKNOWN_ERROR,
- "The databaseName prefixed value doesn't contain a valid database name.");
- }
-
- // Add 1 to include the char size of the DATABASE_DELIMITER
- return prefixedString.substring(0, databaseDelimiterIndex + 1);
- }
-
private static void addToMap(
Map<String, Set<String>> map, String prefix, String prefixedValue) {
Set<String> values = map.get(prefix);
@@ -1746,6 +1595,26 @@
values.add(prefixedValue);
}
+ private static void addToMap(
+ Map<String, Map<String, SchemaTypeConfigProto>> map,
+ String prefix,
+ SchemaTypeConfigProto schemaTypeConfigProto) {
+ Map<String, SchemaTypeConfigProto> schemaTypeMap = map.get(prefix);
+ if (schemaTypeMap == null) {
+ schemaTypeMap = new ArrayMap<>();
+ map.put(prefix, schemaTypeMap);
+ }
+ schemaTypeMap.put(schemaTypeConfigProto.getSchemaType(), schemaTypeConfigProto);
+ }
+
+ private static void removeFromMap(
+ Map<String, Map<String, SchemaTypeConfigProto>> map, String prefix, String schemaType) {
+ Map<String, SchemaTypeConfigProto> schemaTypeMap = map.get(prefix);
+ if (schemaTypeMap != null) {
+ schemaTypeMap.remove(schemaType);
+ }
+ }
+
/**
* Checks the given status code and throws an {@link AppSearchException} if code is an error.
*
@@ -1853,7 +1722,9 @@
/** Remove the rewritten schema types from any result documents. */
@NonNull
@VisibleForTesting
- static SearchResultPage rewriteSearchResultProto(@NonNull SearchResultProto searchResultProto)
+ static SearchResultPage rewriteSearchResultProto(
+ @NonNull SearchResultProto searchResultProto,
+ @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap)
throws AppSearchException {
// Parallel array of package names for each document search result.
List<String> packageNames = new ArrayList<>(searchResultProto.getResultsCount());
@@ -1873,7 +1744,7 @@
resultsBuilder.setResults(i, resultBuilder);
}
return SearchResultToProtoConverter.toSearchResultPage(
- resultsBuilder, packageNames, databaseNames);
+ resultsBuilder, packageNames, databaseNames, schemaMap);
}
@GuardedBy("mReadWriteLock")
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
index 5680670..cdd7952 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchLoggerHelper.java
@@ -18,11 +18,12 @@
import android.annotation.NonNull;
-import com.android.internal.util.Preconditions;
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
import com.google.android.icing.proto.PutDocumentStatsProto;
+import java.util.Objects;
+
/**
* Class contains helper functions for logging.
*
@@ -42,8 +43,8 @@
static void copyNativeStats(
@NonNull PutDocumentStatsProto fromNativeStats,
@NonNull PutDocumentStats.Builder toStatsBuilder) {
- Preconditions.checkNotNull(fromNativeStats);
- Preconditions.checkNotNull(toStatsBuilder);
+ Objects.requireNonNull(fromNativeStats);
+ Objects.requireNonNull(toStatsBuilder);
toStatsBuilder
.setNativeLatencyMillis(fromNativeStats.getLatencyMs())
.setNativeDocumentStoreLatencyMillis(fromNativeStats.getDocumentStoreLatencyMs())
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
index d6b9da8..5ff56ab 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverter.java
@@ -17,16 +17,18 @@
package com.android.server.appsearch.external.localstorage.converter;
import android.annotation.NonNull;
+import android.app.appsearch.AppSearchSchema;
import android.app.appsearch.GenericDocument;
-import com.android.internal.util.Preconditions;
-
import com.google.android.icing.proto.DocumentProto;
import com.google.android.icing.proto.PropertyProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.protobuf.ByteString;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
/**
* Translates a {@link GenericDocument} into a {@link DocumentProto}.
@@ -34,13 +36,20 @@
* @hide
*/
public final class GenericDocumentToProtoConverter {
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final long[] EMPTY_LONG_ARRAY = new long[0];
+ private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
+ private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
+ private static final byte[][] EMPTY_BYTES_ARRAY = new byte[0][0];
+ private static final GenericDocument[] EMPTY_DOCUMENT_ARRAY = new GenericDocument[0];
+
private GenericDocumentToProtoConverter() {}
/** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
@NonNull
@SuppressWarnings("unchecked")
public static DocumentProto toDocumentProto(@NonNull GenericDocument document) {
- Preconditions.checkNotNull(document);
+ Objects.requireNonNull(document);
DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
mProtoBuilder
.setUri(document.getUri())
@@ -97,16 +106,34 @@
return mProtoBuilder.build();
}
- /** Converts a {@link DocumentProto} into a {@link GenericDocument}. */
+ /**
+ * Converts a {@link DocumentProto} into a {@link GenericDocument}.
+ *
+ * <p>In the case that the {@link DocumentProto} object proto has no values set, the converter
+ * searches for the matching property name in the {@link SchemaTypeConfigProto} object for the
+ * document, and infers the correct default value to set for the empty property based on the
+ * data type of the property defined by the schema type.
+ *
+ * @param proto the document to convert to a {@link GenericDocument} instance. The document
+ * proto should have its package + database prefix stripped from its fields.
+ * @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
+ * @param schemaTypeMap map of prefixed schema type to {@link SchemaTypeConfigProto}, used for
+ * looking up the default empty value to set for a document property that has all empty
+ * values.
+ */
@NonNull
- public static GenericDocument toGenericDocument(@NonNull DocumentProto proto) {
- Preconditions.checkNotNull(proto);
+ public static GenericDocument toGenericDocument(
+ @NonNull DocumentProto proto,
+ @NonNull String prefix,
+ @NonNull Map<String, SchemaTypeConfigProto> schemaTypeMap) {
+ Objects.requireNonNull(proto);
GenericDocument.Builder<?> documentBuilder =
new GenericDocument.Builder<>(
proto.getNamespace(), proto.getUri(), proto.getSchema())
.setScore(proto.getScore())
.setTtlMillis(proto.getTtlMs())
.setCreationTimestampMillis(proto.getCreationTimestampMs());
+ String prefixedSchemaType = prefix + proto.getSchema();
for (int i = 0; i < proto.getPropertiesCount(); i++) {
PropertyProto property = proto.getProperties(i);
@@ -144,13 +171,51 @@
} else if (property.getDocumentValuesCount() > 0) {
GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
for (int j = 0; j < values.length; j++) {
- values[j] = toGenericDocument(property.getDocumentValues(j));
+ values[j] =
+ toGenericDocument(property.getDocumentValues(j), prefix, schemaTypeMap);
}
documentBuilder.setPropertyDocument(name, values);
} else {
- throw new IllegalStateException("Unknown type of value: " + name);
+ // TODO(b/184966497): Optimize by caching PropertyConfigProto
+ setEmptyProperty(name, documentBuilder, schemaTypeMap.get(prefixedSchemaType));
}
}
return documentBuilder.build();
}
+
+ private static void setEmptyProperty(
+ @NonNull String propertyName,
+ @NonNull GenericDocument.Builder<?> documentBuilder,
+ @NonNull SchemaTypeConfigProto schema) {
+ @AppSearchSchema.PropertyConfig.DataType int dataType = 0;
+ for (int i = 0; i < schema.getPropertiesCount(); ++i) {
+ if (propertyName.equals(schema.getProperties(i).getPropertyName())) {
+ dataType = schema.getProperties(i).getDataType().getNumber();
+ break;
+ }
+ }
+
+ switch (dataType) {
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
+ documentBuilder.setPropertyString(propertyName, EMPTY_STRING_ARRAY);
+ break;
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_INT64:
+ documentBuilder.setPropertyLong(propertyName, EMPTY_LONG_ARRAY);
+ break;
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
+ documentBuilder.setPropertyDouble(propertyName, EMPTY_DOUBLE_ARRAY);
+ break;
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
+ documentBuilder.setPropertyBoolean(propertyName, EMPTY_BOOLEAN_ARRAY);
+ break;
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
+ documentBuilder.setPropertyBytes(propertyName, EMPTY_BYTES_ARRAY);
+ break;
+ case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
+ documentBuilder.setPropertyDocument(propertyName, EMPTY_DOCUMENT_ARRAY);
+ break;
+ default:
+ throw new IllegalStateException("Unknown type of value: " + propertyName);
+ }
+ }
}
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 800b073..e3fa7e0 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
@@ -20,8 +20,6 @@
import android.app.appsearch.AppSearchSchema;
import android.util.Log;
-import com.android.internal.util.Preconditions;
-
import com.google.android.icing.proto.DocumentIndexingConfig;
import com.google.android.icing.proto.PropertyConfigProto;
import com.google.android.icing.proto.SchemaTypeConfigProto;
@@ -30,6 +28,7 @@
import com.google.android.icing.proto.TermMatchType;
import java.util.List;
+import java.util.Objects;
/**
* Translates an {@link AppSearchSchema} into a {@link SchemaTypeConfigProto}.
@@ -48,7 +47,7 @@
@NonNull
public static SchemaTypeConfigProto toSchemaTypeConfigProto(
@NonNull AppSearchSchema schema, int version) {
- Preconditions.checkNotNull(schema);
+ Objects.requireNonNull(schema);
SchemaTypeConfigProto.Builder protoBuilder =
SchemaTypeConfigProto.newBuilder()
.setSchemaType(schema.getSchemaType())
@@ -64,7 +63,7 @@
@NonNull
private static PropertyConfigProto toPropertyConfigProto(
@NonNull AppSearchSchema.PropertyConfig property) {
- Preconditions.checkNotNull(property);
+ Objects.requireNonNull(property);
PropertyConfigProto.Builder builder =
PropertyConfigProto.newBuilder().setPropertyName(property.getName());
@@ -116,7 +115,7 @@
*/
@NonNull
public static AppSearchSchema toAppSearchSchema(@NonNull SchemaTypeConfigProtoOrBuilder proto) {
- Preconditions.checkNotNull(proto);
+ Objects.requireNonNull(proto);
AppSearchSchema.Builder builder = new AppSearchSchema.Builder(proto.getSchemaType());
List<PropertyConfigProto> properties = proto.getPropertiesList();
for (int i = 0; i < properties.size(); i++) {
@@ -129,7 +128,7 @@
@NonNull
private static AppSearchSchema.PropertyConfig toPropertyConfig(
@NonNull PropertyConfigProto proto) {
- Preconditions.checkNotNull(proto);
+ Objects.requireNonNull(proto);
switch (proto.getDataType()) {
case STRING:
return toStringPropertyConfig(proto);
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 bf7e533..57c1590 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
@@ -16,6 +16,8 @@
package com.android.server.appsearch.external.localstorage.converter;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+
import android.annotation.NonNull;
import android.app.appsearch.GenericDocument;
import android.app.appsearch.SearchResult;
@@ -24,6 +26,7 @@
import com.android.internal.util.Preconditions;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
import com.google.android.icing.proto.SearchResultProto;
import com.google.android.icing.proto.SearchResultProtoOrBuilder;
import com.google.android.icing.proto.SnippetMatchProto;
@@ -31,6 +34,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* Translates a {@link SearchResultProto} into {@link SearchResult}s.
@@ -49,13 +53,17 @@
* @param databaseNames A parallel array of database names. The database name at index 'i' of
* this list shold be the database that indexed the document at index 'i' of
* proto.getResults(i).
+ * @param schemaMap A map of prefixes to an inner-map of prefixed schema type to
+ * SchemaTypeConfigProtos, used for setting a default value for results with DocumentProtos
+ * that have empty values.
* @return {@link SearchResultPage} of results.
*/
@NonNull
public static SearchResultPage toSearchResultPage(
@NonNull SearchResultProtoOrBuilder proto,
@NonNull List<String> packageNames,
- @NonNull List<String> databaseNames) {
+ @NonNull List<String> databaseNames,
+ @NonNull Map<String, Map<String, SchemaTypeConfigProto>> schemaMap) {
Preconditions.checkArgument(
proto.getResultsCount() == packageNames.size(),
"Size of results does not match the number of package names.");
@@ -63,8 +71,14 @@
bundle.putLong(SearchResultPage.NEXT_PAGE_TOKEN_FIELD, proto.getNextPageToken());
ArrayList<Bundle> resultBundles = new ArrayList<>(proto.getResultsCount());
for (int i = 0; i < proto.getResultsCount(); i++) {
+ String prefix = createPrefix(packageNames.get(i), databaseNames.get(i));
+ Map<String, SchemaTypeConfigProto> schemaTypeMap = schemaMap.get(prefix);
SearchResult result =
- toSearchResult(proto.getResults(i), packageNames.get(i), databaseNames.get(i));
+ toSearchResult(
+ proto.getResults(i),
+ packageNames.get(i),
+ databaseNames.get(i),
+ schemaTypeMap);
resultBundles.add(result.getBundle());
}
bundle.putParcelableArrayList(SearchResultPage.RESULTS_FIELD, resultBundles);
@@ -77,15 +91,21 @@
* @param proto The proto to be converted.
* @param packageName The package name associated with the document in {@code proto}.
* @param databaseName The database name associated with the document in {@code proto}.
+ * @param schemaTypeToProtoMap A map of prefixed schema types to their corresponding
+ * SchemaTypeConfigProto, used for setting a default value for results with DocumentProtos
+ * that have empty values.
* @return A {@link SearchResult} bundle.
*/
@NonNull
private static SearchResult toSearchResult(
@NonNull SearchResultProto.ResultProtoOrBuilder proto,
@NonNull String packageName,
- @NonNull String databaseName) {
+ @NonNull String databaseName,
+ @NonNull Map<String, SchemaTypeConfigProto> schemaTypeToProtoMap) {
+ String prefix = createPrefix(packageName, databaseName);
GenericDocument document =
- GenericDocumentToProtoConverter.toGenericDocument(proto.getDocument());
+ GenericDocumentToProtoConverter.toGenericDocument(
+ proto.getDocument(), prefix, schemaTypeToProtoMap);
SearchResult.Builder builder =
new SearchResult.Builder(packageName, databaseName)
.setGenericDocument(document)
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 d9e8adb..8f9e9bd 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
@@ -19,13 +19,13 @@
import android.annotation.NonNull;
import android.app.appsearch.SearchSpec;
-import com.android.internal.util.Preconditions;
-
import com.google.android.icing.proto.ResultSpecProto;
import com.google.android.icing.proto.ScoringSpecProto;
import com.google.android.icing.proto.SearchSpecProto;
import com.google.android.icing.proto.TermMatchType;
+import java.util.Objects;
+
/**
* Translates a {@link SearchSpec} into icing search protos.
*
@@ -37,7 +37,7 @@
/** Extracts {@link SearchSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static SearchSpecProto toSearchSpecProto(@NonNull SearchSpec spec) {
- Preconditions.checkNotNull(spec);
+ Objects.requireNonNull(spec);
SearchSpecProto.Builder protoBuilder =
SearchSpecProto.newBuilder()
.addAllSchemaTypeFilters(spec.getFilterSchemas())
@@ -56,7 +56,7 @@
/** Extracts {@link ResultSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static ResultSpecProto toResultSpecProto(@NonNull SearchSpec spec) {
- Preconditions.checkNotNull(spec);
+ Objects.requireNonNull(spec);
return ResultSpecProto.newBuilder()
.setNumPerPage(spec.getResultCountPerPage())
.setSnippetSpec(
@@ -73,7 +73,7 @@
/** Extracts {@link ScoringSpecProto} information from a {@link SearchSpec}. */
@NonNull
public static ScoringSpecProto toScoringSpecProto(@NonNull SearchSpec spec) {
- Preconditions.checkNotNull(spec);
+ Objects.requireNonNull(spec);
ScoringSpecProto.Builder protoBuilder = ScoringSpecProto.newBuilder();
@SearchSpec.Order int orderCode = spec.getOrder();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
index a0f39ec..ed73593 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/SetSchemaResponseToProtoConverter.java
@@ -19,10 +19,10 @@
import android.annotation.NonNull;
import android.app.appsearch.SetSchemaResponse;
-import com.android.internal.util.Preconditions;
-
import com.google.android.icing.proto.SetSchemaResultProto;
+import java.util.Objects;
+
/**
* Translates a {@link SetSchemaResultProto} into {@link SetSchemaResponse}.
*
@@ -42,8 +42,8 @@
@NonNull
public static SetSchemaResponse toSetSchemaResponse(
@NonNull SetSchemaResultProto proto, @NonNull String prefix) {
- Preconditions.checkNotNull(proto);
- Preconditions.checkNotNull(prefix);
+ Objects.requireNonNull(proto);
+ Objects.requireNonNull(prefix);
SetSchemaResponse.Builder builder = new SetSchemaResponse.Builder();
for (int i = 0; i < proto.getDeletedSchemaTypesCount(); i++) {
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
index 6f6dad2..acf04ef 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/converter/TypePropertyPathToProtoConverter.java
@@ -18,13 +18,12 @@
import android.annotation.NonNull;
-import com.android.internal.util.Preconditions;
-
import com.google.android.icing.proto.TypePropertyMask;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Translates a <code>Map<String, List<String>></code> into <code>List<TypePropertyMask></code>.
@@ -38,7 +37,7 @@
@NonNull
public static List<TypePropertyMask> toTypePropertyMaskList(
@NonNull Map<String, List<String>> typePropertyPaths) {
- Preconditions.checkNotNull(typePropertyPaths);
+ Objects.requireNonNull(typePropertyPaths);
List<TypePropertyMask> typePropertyMasks = new ArrayList<>(typePropertyPaths.size());
for (Map.Entry<String, List<String>> e : typePropertyPaths.entrySet()) {
typePropertyMasks.add(
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
index a724f95..cf640c1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/CallStats.java
@@ -19,10 +19,9 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* A class for setting basic information to log for all function calls.
@@ -75,8 +74,8 @@
private final int mNumOperationsFailed;
CallStats(@NonNull Builder builder) {
- Preconditions.checkNotNull(builder);
- mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
+ Objects.requireNonNull(builder);
+ mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
mCallType = builder.mCallType;
mEstimatedBinderLatencyMillis = builder.mEstimatedBinderLatencyMillis;
mNumOperationsSucceeded = builder.mNumOperationsSucceeded;
@@ -140,8 +139,8 @@
/** Builder takes {@link GeneralStats.Builder}. */
public Builder(@NonNull String packageName, @NonNull String database) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(database);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(database);
mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
index 8ce8eda..53c1ee3 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/GeneralStats.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.app.appsearch.AppSearchResult;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* A class for holding general logging information.
@@ -48,9 +48,9 @@
private final int mTotalLatencyMillis;
GeneralStats(@NonNull Builder builder) {
- Preconditions.checkNotNull(builder);
- mPackageName = Preconditions.checkNotNull(builder.mPackageName);
- mDatabase = Preconditions.checkNotNull(builder.mDatabase);
+ Objects.requireNonNull(builder);
+ mPackageName = Objects.requireNonNull(builder.mPackageName);
+ mDatabase = Objects.requireNonNull(builder.mDatabase);
mStatusCode = builder.mStatusCode;
mTotalLatencyMillis = builder.mTotalLatencyMillis;
}
@@ -92,8 +92,8 @@
* @param database name of the database logging stats
*/
public Builder(@NonNull String packageName, @NonNull String database) {
- mPackageName = Preconditions.checkNotNull(packageName);
- mDatabase = Preconditions.checkNotNull(database);
+ mPackageName = Objects.requireNonNull(packageName);
+ mDatabase = Objects.requireNonNull(database);
}
/** Sets status code returned from {@link AppSearchResult#getResultCode()} */
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
index c1f6fb1..d031172 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/stats/PutDocumentStats.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
-import com.android.internal.util.Preconditions;
+import java.util.Objects;
/**
* A class for holding detailed stats to log for each individual document put by a {@link
@@ -60,8 +60,8 @@
private final boolean mNativeExceededMaxNumTokens;
PutDocumentStats(@NonNull Builder builder) {
- Preconditions.checkNotNull(builder);
- mGeneralStats = Preconditions.checkNotNull(builder.mGeneralStatsBuilder).build();
+ Objects.requireNonNull(builder);
+ mGeneralStats = Objects.requireNonNull(builder.mGeneralStatsBuilder).build();
mGenerateDocumentProtoLatencyMillis = builder.mGenerateDocumentProtoLatencyMillis;
mRewriteDocumentTypesLatencyMillis = builder.mRewriteDocumentTypesLatencyMillis;
mNativeLatencyMillis = builder.mNativeLatencyMillis;
@@ -142,8 +142,8 @@
/** Builder takes {@link GeneralStats.Builder}. */
public Builder(@NonNull String packageName, @NonNull String database) {
- Preconditions.checkNotNull(packageName);
- Preconditions.checkNotNull(database);
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(database);
mGeneralStatsBuilder = new GeneralStats.Builder(packageName, database);
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java
new file mode 100644
index 0000000..9ae9f18
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/util/PrefixUtil.java
@@ -0,0 +1,229 @@
+/*
+ * 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 com.android.server.appsearch.external.localstorage.util;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.exceptions.AppSearchException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+/**
+ * Provides utility functions for working with package + database prefixes.
+ *
+ * @hide
+ */
+public class PrefixUtil {
+ private static final String TAG = "AppSearchPrefixUtil";
+
+ @VisibleForTesting public static final char DATABASE_DELIMITER = '/';
+
+ @VisibleForTesting public static final char PACKAGE_DELIMITER = '$';
+
+ private PrefixUtil() {}
+
+ /** Creates prefix string for given package name and database name. */
+ @NonNull
+ public static String createPrefix(@NonNull String packageName, @NonNull String databaseName) {
+ return packageName + PACKAGE_DELIMITER + databaseName + DATABASE_DELIMITER;
+ }
+ /** Creates prefix string for given package name. */
+ @NonNull
+ public static String createPackagePrefix(@NonNull String packageName) {
+ return packageName + PACKAGE_DELIMITER;
+ }
+
+ /**
+ * Returns the package name that's contained within the {@code prefix}.
+ *
+ * @param prefix Prefix string that contains the package name inside of it. The package name
+ * must be in the front of the string, and separated from the rest of the string by the
+ * {@link #PACKAGE_DELIMITER}.
+ * @return Valid package name.
+ */
+ @NonNull
+ public static String getPackageName(@NonNull String prefix) {
+ int delimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+ if (delimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
+ return "";
+ }
+ return prefix.substring(0, delimiterIndex);
+ }
+
+ /**
+ * Returns the database name that's contained within the {@code prefix}.
+ *
+ * @param prefix Prefix string that contains the database name inside of it. The database name
+ * must be between the {@link #PACKAGE_DELIMITER} and {@link #DATABASE_DELIMITER}
+ * @return Valid database name.
+ */
+ @NonNull
+ public static String getDatabaseName(@NonNull String prefix) {
+ // TODO (b/184050178) Start database delimiter index search from after package delimiter
+ int packageDelimiterIndex = prefix.indexOf(PACKAGE_DELIMITER);
+ int databaseDelimiterIndex = prefix.indexOf(DATABASE_DELIMITER);
+ if (packageDelimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain package delimiter: " + prefix);
+ return "";
+ }
+ if (databaseDelimiterIndex == -1) {
+ // This should never happen if we construct our prefixes properly
+ Log.wtf(TAG, "Malformed prefix doesn't contain database delimiter: " + prefix);
+ return "";
+ }
+ return prefix.substring(packageDelimiterIndex + 1, databaseDelimiterIndex);
+ }
+
+ /**
+ * Creates a string with the package and database prefix removed from the input string.
+ *
+ * @param prefixedString a string containing a package and database prefix.
+ * @return a string with the package and database prefix removed.
+ * @throws AppSearchException if the prefixed value does not contain a valid database name.
+ */
+ @NonNull
+ public static String removePrefix(@NonNull String prefixedString) throws AppSearchException {
+ // The prefix is made up of the package, then the database. So we only need to find the
+ // database cutoff.
+ int delimiterIndex;
+ if ((delimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER)) != -1) {
+ // Add 1 to include the char size of the DATABASE_DELIMITER
+ return prefixedString.substring(delimiterIndex + 1);
+ }
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "The prefixed value doesn't contains a valid database name.");
+ }
+
+ /**
+ * Creates a package and database prefix string from the input string.
+ *
+ * @param prefixedString a string containing a package and database prefix.
+ * @return a string with the package and database prefix
+ * @throws AppSearchException if the prefixed value does not contain a valid database name.
+ */
+ @NonNull
+ public static String getPrefix(@NonNull String prefixedString) throws AppSearchException {
+ int databaseDelimiterIndex = prefixedString.indexOf(DATABASE_DELIMITER);
+ if (databaseDelimiterIndex == -1) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_UNKNOWN_ERROR,
+ "The databaseName prefixed value doesn't contain a valid database name.");
+ }
+
+ // Add 1 to include the char size of the DATABASE_DELIMITER
+ return prefixedString.substring(0, databaseDelimiterIndex + 1);
+ }
+
+ /**
+ * Prepends {@code prefix} to all types and namespaces mentioned anywhere in {@code
+ * documentBuilder}.
+ *
+ * @param documentBuilder The document to mutate
+ * @param prefix The prefix to add
+ */
+ public static void addPrefixToDocument(
+ @NonNull DocumentProto.Builder documentBuilder, @NonNull String prefix) {
+ // Rewrite the type name to include/remove the prefix.
+ String newSchema = prefix + documentBuilder.getSchema();
+ documentBuilder.setSchema(newSchema);
+
+ // Rewrite the namespace to include/remove the prefix.
+ documentBuilder.setNamespace(prefix + documentBuilder.getNamespace());
+
+ // Recurse into derived documents
+ for (int propertyIdx = 0;
+ propertyIdx < documentBuilder.getPropertiesCount();
+ propertyIdx++) {
+ int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+ if (documentCount > 0) {
+ PropertyProto.Builder propertyBuilder =
+ documentBuilder.getProperties(propertyIdx).toBuilder();
+ for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+ DocumentProto.Builder derivedDocumentBuilder =
+ propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+ addPrefixToDocument(derivedDocumentBuilder, prefix);
+ propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+ }
+ documentBuilder.setProperties(propertyIdx, propertyBuilder);
+ }
+ }
+ }
+
+ /**
+ * Removes any prefixes from types and namespaces mentioned anywhere in {@code documentBuilder}.
+ *
+ * @param documentBuilder The document to mutate
+ * @return Prefix name that was removed from the document.
+ * @throws AppSearchException if there are unexpected database prefixing errors.
+ */
+ @NonNull
+ public static String removePrefixesFromDocument(@NonNull DocumentProto.Builder documentBuilder)
+ throws AppSearchException {
+ // Rewrite the type name and namespace to remove the prefix.
+ String schemaPrefix = getPrefix(documentBuilder.getSchema());
+ String namespacePrefix = getPrefix(documentBuilder.getNamespace());
+
+ if (!schemaPrefix.equals(namespacePrefix)) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INTERNAL_ERROR,
+ "Found unexpected"
+ + " multiple prefix names in document: "
+ + schemaPrefix
+ + ", "
+ + namespacePrefix);
+ }
+
+ documentBuilder.setSchema(removePrefix(documentBuilder.getSchema()));
+ documentBuilder.setNamespace(removePrefix(documentBuilder.getNamespace()));
+
+ // Recurse into derived documents
+ for (int propertyIdx = 0;
+ propertyIdx < documentBuilder.getPropertiesCount();
+ propertyIdx++) {
+ int documentCount = documentBuilder.getProperties(propertyIdx).getDocumentValuesCount();
+ if (documentCount > 0) {
+ PropertyProto.Builder propertyBuilder =
+ documentBuilder.getProperties(propertyIdx).toBuilder();
+ for (int documentIdx = 0; documentIdx < documentCount; documentIdx++) {
+ DocumentProto.Builder derivedDocumentBuilder =
+ propertyBuilder.getDocumentValues(documentIdx).toBuilder();
+ String nestedPrefix = removePrefixesFromDocument(derivedDocumentBuilder);
+ if (!nestedPrefix.equals(schemaPrefix)) {
+ throw new AppSearchException(
+ AppSearchResult.RESULT_INTERNAL_ERROR,
+ "Found unexpected multiple prefix names in document: "
+ + schemaPrefix
+ + ", "
+ + nestedPrefix);
+ }
+ propertyBuilder.setDocumentValues(documentIdx, derivedDocumentBuilder);
+ }
+ documentBuilder.setProperties(propertyIdx, propertyBuilder);
+ }
+ }
+
+ return schemaPrefix;
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
index 1c04d99..731ab35 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java
@@ -29,7 +29,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.Preconditions;
import com.android.server.appsearch.external.localstorage.AppSearchLogger;
import com.android.server.appsearch.external.localstorage.stats.CallStats;
import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats;
@@ -38,6 +37,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
/**
@@ -162,15 +162,15 @@
* Westworld constructor
*/
public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) {
- mContext = Preconditions.checkNotNull(context);
- mConfig = Preconditions.checkNotNull(config);
+ mContext = Objects.requireNonNull(context);
+ mConfig = Objects.requireNonNull(config);
mUserId = userId;
}
/** Logs {@link CallStats}. */
@Override
public void logStats(@NonNull CallStats stats) {
- Preconditions.checkNotNull(stats);
+ Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(stats.getCallType())) {
logStatsImplLocked(stats);
@@ -181,7 +181,7 @@
/** Logs {@link PutDocumentStats}. */
@Override
public void logStats(@NonNull PutDocumentStats stats) {
- Preconditions.checkNotNull(stats);
+ Objects.requireNonNull(stats);
synchronized (mLock) {
if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) {
logStatsImplLocked(stats);
@@ -197,7 +197,7 @@
*/
public int removeCachedUidForPackage(@NonNull String packageName) {
// TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
synchronized (mLock) {
Integer uid = mPackageUidCacheLocked.remove(packageName);
return uid != null ? uid : Process.INVALID_UID;
diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt
index e46c147..f99664b 100644
--- a/apex/appsearch/synced_jetpack_changeid.txt
+++ b/apex/appsearch/synced_jetpack_changeid.txt
@@ -1 +1 @@
-I925ec12f4901c7759976c344ba3428210aada8ad
+If9d1d770d2327d7d0db7d82acfc54787b5de64bc
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 f0de496..6193367 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
@@ -40,11 +40,11 @@
import androidx.test.core.app.ApplicationProvider;
-import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -81,8 +81,8 @@
private AppSearchSessionShimImpl(
@NonNull AppSearchSession session, @NonNull ExecutorService executor) {
- mAppSearchSession = Preconditions.checkNotNull(session);
- mExecutor = Preconditions.checkNotNull(executor);
+ mAppSearchSession = Objects.requireNonNull(session);
+ mExecutor = Objects.requireNonNull(executor);
}
@Override
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 5042ce0..c35849d 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
@@ -30,11 +30,11 @@
import androidx.test.core.app.ApplicationProvider;
-import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
+import java.util.Objects;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -69,8 +69,8 @@
private GlobalSearchSessionShimImpl(
@NonNull GlobalSearchSession session, @NonNull ExecutorService executor) {
- mGlobalSearchSession = Preconditions.checkNotNull(session);
- mExecutor = Preconditions.checkNotNull(executor);
+ mGlobalSearchSession = Objects.requireNonNull(session);
+ mExecutor = Objects.requireNonNull(executor);
}
@NonNull
diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
index 5f26e8c..72078f8 100644
--- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
+++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java
@@ -22,12 +22,12 @@
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchResultsShim;
-import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -40,8 +40,8 @@
private final SearchResults mSearchResults;
SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) {
- mExecutor = Preconditions.checkNotNull(executor);
- mSearchResults = Preconditions.checkNotNull(searchResults);
+ mExecutor = Objects.requireNonNull(executor);
+ mSearchResults = Objects.requireNonNull(searchResults);
}
@NonNull
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 2069043..494945d 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
@@ -187,9 +187,8 @@
* <p>Removed documents will no longer be surfaced by {@link #search} or {@link #getByUri}
* calls.
*
- * <p><b>NOTE:</b>By default, documents are removed via a soft delete operation. Once the
- * document crosses the count threshold or byte usage threshold, the documents will be removed
- * from disk.
+ * <p>Once the database crosses the document count or byte usage threshold, removed documents
+ * will be deleted from disk.
*
* @param request {@link RemoveByUriRequest} with URIs and namespace to remove from the index.
* @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}. The
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index 113f8fe..6dbbcb5 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -26,8 +26,8 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Base64;
+import android.util.IndentingPrintWriter;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
index ca588c5..09260b7 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
@@ -37,9 +37,9 @@
import android.util.ArraySet;
import android.util.Base64;
import android.util.DebugUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 8b12beb..e477156 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -15,6 +15,7 @@
*/
package com.android.server.blob;
+import static android.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS;
import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
@@ -36,6 +37,7 @@
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
@@ -45,15 +47,18 @@
import android.app.blob.BlobHandle;
import android.app.blob.LeaseInfo;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.RevocableFileDescriptor;
import android.os.UserHandle;
+import android.permission.PermissionManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsEvent;
@@ -62,7 +67,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.XmlUtils;
import com.android.server.blob.BlobStoreManagerService.DumpArgs;
@@ -85,7 +89,6 @@
private final long mBlobId;
private final BlobHandle mBlobHandle;
- private final int mUserId;
@GuardedBy("mMetadataLock")
private final ArraySet<Committer> mCommitters = new ArraySet<>();
@@ -94,24 +97,23 @@
private final ArraySet<Leasee> mLeasees = new ArraySet<>();
/**
- * Contains packageName -> {RevocableFileDescriptors}.
+ * Contains Accessor -> {RevocableFileDescriptors}.
*
* Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so
* that when clients access is revoked or the blob gets deleted, we can be sure that clients
* do not have any reference to the blob and the space occupied by the blob can be freed.
*/
@GuardedBy("mRevocableFds")
- private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds =
+ private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds =
new ArrayMap<>();
// Do not access this directly, instead use #getBlobFile().
private File mBlobFile;
- BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) {
+ BlobMetadata(Context context, long blobId, BlobHandle blobHandle) {
mContext = context;
this.mBlobId = blobId;
this.mBlobHandle = blobHandle;
- this.mUserId = userId;
}
long getBlobId() {
@@ -122,10 +124,6 @@
return mBlobHandle;
}
- int getUserId() {
- return mUserId;
- }
-
void addOrReplaceCommitter(@NonNull Committer committer) {
synchronized (mMetadataLock) {
// We need to override the committer data, so first remove any existing
@@ -155,13 +153,24 @@
}
}
- void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) {
+ void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
synchronized (mMetadataLock) {
- mCommitters.removeIf(committer ->
- !committer.packageName.equals(knownPackages.get(committer.uid)));
+ mCommitters.removeIf(committer -> {
+ final int userId = UserHandle.getUserId(committer.uid);
+ final SparseArray<String> userPackages = knownPackages.get(userId);
+ if (userPackages == null) {
+ return true;
+ }
+ return !committer.packageName.equals(userPackages.get(committer.uid));
+ });
}
}
+ void addCommittersAndLeasees(BlobMetadata blobMetadata) {
+ mCommitters.addAll(blobMetadata.mCommitters);
+ mLeasees.addAll(blobMetadata.mLeasees);
+ }
+
@Nullable
Committer getExistingCommitter(@NonNull String packageName, int uid) {
synchronized (mCommitters) {
@@ -201,10 +210,16 @@
}
}
- void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) {
+ void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) {
synchronized (mMetadataLock) {
- mLeasees.removeIf(leasee ->
- !leasee.packageName.equals(knownPackages.get(leasee.uid)));
+ mLeasees.removeIf(leasee -> {
+ final int userId = UserHandle.getUserId(leasee.uid);
+ final SparseArray<String> userPackages = knownPackages.get(userId);
+ if (userPackages == null) {
+ return true;
+ }
+ return !leasee.packageName.equals(userPackages.get(leasee.uid));
+ });
}
}
@@ -214,6 +229,25 @@
}
}
+ void removeDataForUser(int userId) {
+ synchronized (mMetadataLock) {
+ mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid)));
+ mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid)));
+ mRevocableFds.entrySet().removeIf(entry -> {
+ final Accessor accessor = entry.getKey();
+ final ArraySet<RevocableFileDescriptor> rFds = entry.getValue();
+ if (userId != UserHandle.getUserId(accessor.uid)) {
+ return false;
+ }
+ for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) {
+ rFds.valueAt(i).revoke();
+ }
+ rFds.clear();
+ return true;
+ });
+ }
+ }
+
boolean hasValidLeases() {
synchronized (mMetadataLock) {
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
@@ -244,8 +278,12 @@
}
}
+ final int callingUserId = UserHandle.getUserId(callingUid);
for (int i = 0, size = mCommitters.size(); i < size; ++i) {
final Committer committer = mCommitters.valueAt(i);
+ if (callingUserId != UserHandle.getUserId(committer.uid)) {
+ continue;
+ }
// Check if the caller is the same package that committed the blob.
if (committer.equals(callingPackage, callingUid)) {
@@ -259,38 +297,105 @@
return true;
}
}
+
+ final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers(
+ callingPackage, callingUserId);
+ if (!canCallerAccessBlobsAcrossUsers) {
+ return false;
+ }
+ for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+ final Committer committer = mCommitters.valueAt(i);
+ final int committerUserId = UserHandle.getUserId(committer.uid);
+ if (callingUserId == committerUserId) {
+ continue;
+ }
+ if (!checkCallerCanAccessBlobsAcrossUsers(callingPackage, committerUserId)) {
+ continue;
+ }
+
+ // Check if the caller is allowed access as per the access mode specified
+ // by the committer.
+ if (committer.blobAccessMode.isAccessAllowedForCaller(mContext,
+ callingPackage, committer.packageName, callingUid, attributionTag)) {
+ return true;
+ }
+ }
+
+ }
+ return false;
+ }
+
+ private static boolean checkCallerCanAccessBlobsAcrossUsers(
+ String callingPackage, int callingUserId) {
+ return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS,
+ callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ boolean hasACommitterOrLeaseeInUser(int userId) {
+ return hasACommitterInUser(userId) || hasALeaseeInUser(userId);
+ }
+
+ boolean hasACommitterInUser(int userId) {
+ synchronized (mMetadataLock) {
+ for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+ final Committer committer = mCommitters.valueAt(i);
+ if (userId == UserHandle.getUserId(committer.uid)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasALeaseeInUser(int userId) {
+ synchronized (mMetadataLock) {
+ for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+ final Leasee leasee = mLeasees.valueAt(i);
+ if (userId == UserHandle.getUserId(leasee.uid)) {
+ return true;
+ }
+ }
}
return false;
}
boolean isACommitter(@NonNull String packageName, int uid) {
synchronized (mMetadataLock) {
- return isAnAccessor(mCommitters, packageName, uid);
+ return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid));
}
}
boolean isALeasee(@Nullable String packageName, int uid) {
synchronized (mMetadataLock) {
- final Leasee leasee = getAccessor(mLeasees, packageName, uid);
+ final Leasee leasee = getAccessor(mLeasees, packageName, uid,
+ UserHandle.getUserId(uid));
+ return leasee != null && leasee.isStillValid();
+ }
+ }
+
+ private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) {
+ synchronized (mMetadataLock) {
+ final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId);
return leasee != null && leasee.isStillValid();
}
}
private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors,
- @Nullable String packageName, int uid) {
+ @Nullable String packageName, int uid, int userId) {
// Check if the package is an accessor of the data blob.
- return getAccessor(accessors, packageName, uid) != null;
+ return getAccessor(accessors, packageName, uid, userId) != null;
}
private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors,
- @Nullable String packageName, int uid) {
+ @Nullable String packageName, int uid, int userId) {
// Check if the package is an accessor of the data blob.
for (int i = 0, size = accessors.size(); i < size; ++i) {
final Accessor accessor = accessors.valueAt(i);
if (packageName != null && uid != INVALID_UID
&& accessor.equals(packageName, uid)) {
return (T) accessor;
- } else if (packageName != null && accessor.packageName.equals(packageName)) {
+ } else if (packageName != null && accessor.packageName.equals(packageName)
+ && userId == UserHandle.getUserId(accessor.uid)) {
return (T) accessor;
} else if (uid != INVALID_UID && accessor.uid == uid) {
return (T) accessor;
@@ -299,23 +404,29 @@
return null;
}
- boolean isALeasee(@NonNull String packageName) {
- return isALeasee(packageName, INVALID_UID);
+ boolean shouldAttributeToLeasee(@NonNull String packageName, int userId,
+ boolean callerHasStatsPermission) {
+ if (!isALeaseeInUser(packageName, INVALID_UID, userId)) {
+ return false;
+ }
+ if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) {
+ return true;
+ }
+ return false;
}
- boolean isALeasee(int uid) {
- return isALeasee(null, uid);
+ boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) {
+ final int userId = UserHandle.getUserId(uid);
+ if (!isALeaseeInUser(null, uid, userId)) {
+ return false;
+ }
+ if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) {
+ return true;
+ }
+ return false;
}
- boolean hasOtherLeasees(@NonNull String packageName) {
- return hasOtherLeasees(packageName, INVALID_UID);
- }
-
- boolean hasOtherLeasees(int uid) {
- return hasOtherLeasees(null, uid);
- }
-
- private boolean hasOtherLeasees(@Nullable String packageName, int uid) {
+ private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) {
synchronized (mMetadataLock) {
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
final Leasee leasee = mLeasees.valueAt(i);
@@ -326,7 +437,8 @@
if (packageName != null && uid != INVALID_UID
&& !leasee.equals(packageName, uid)) {
return true;
- } else if (packageName != null && !leasee.packageName.equals(packageName)) {
+ } else if (packageName != null && (!leasee.packageName.equals(packageName)
+ || userId != UserHandle.getUserId(leasee.uid))) {
return true;
} else if (uid != INVALID_UID && leasee.uid != uid) {
return true;
@@ -371,7 +483,7 @@
return mBlobFile;
}
- ParcelFileDescriptor openForRead(String callingPackage) throws IOException {
+ ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException {
// TODO: Add limit on opened fds
FileDescriptor fd;
try {
@@ -381,7 +493,7 @@
}
try {
if (BlobStoreConfig.shouldUseRevocableFdForReads()) {
- return createRevocableFd(fd, callingPackage);
+ return createRevocableFd(fd, callingPackage, callingUid);
} else {
return new ParcelFileDescriptor(fd);
}
@@ -393,26 +505,28 @@
@NonNull
private ParcelFileDescriptor createRevocableFd(FileDescriptor fd,
- String callingPackage) throws IOException {
+ String callingPackage, int callingUid) throws IOException {
final RevocableFileDescriptor revocableFd =
new RevocableFileDescriptor(mContext, fd);
+ final Accessor accessor;
synchronized (mRevocableFds) {
- ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
- mRevocableFds.get(callingPackage);
- if (revocableFdsForPkg == null) {
- revocableFdsForPkg = new ArraySet<>();
- mRevocableFds.put(callingPackage, revocableFdsForPkg);
+ accessor = new Accessor(callingPackage, callingUid);
+ ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
+ mRevocableFds.get(accessor);
+ if (revocableFdsForAccessor == null) {
+ revocableFdsForAccessor = new ArraySet<>();
+ mRevocableFds.put(accessor, revocableFdsForAccessor);
}
- revocableFdsForPkg.add(revocableFd);
+ revocableFdsForAccessor.add(revocableFd);
}
revocableFd.addOnCloseListener((e) -> {
synchronized (mRevocableFds) {
- final ArraySet<RevocableFileDescriptor> revocableFdsForPkg =
- mRevocableFds.get(callingPackage);
- if (revocableFdsForPkg != null) {
- revocableFdsForPkg.remove(revocableFd);
- if (revocableFdsForPkg.isEmpty()) {
- mRevocableFds.remove(callingPackage);
+ final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor =
+ mRevocableFds.get(accessor);
+ if (revocableFdsForAccessor != null) {
+ revocableFdsForAccessor.remove(revocableFd);
+ if (revocableFdsForAccessor.isEmpty()) {
+ mRevocableFds.remove(accessor);
}
}
}
@@ -421,22 +535,23 @@
}
void destroy() {
- revokeAllFds();
+ revokeAndClearAllFds();
getBlobFile().delete();
}
- private void revokeAllFds() {
+ private void revokeAndClearAllFds() {
synchronized (mRevocableFds) {
- for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) {
- final ArraySet<RevocableFileDescriptor> packageFds =
+ for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) {
+ final ArraySet<RevocableFileDescriptor> rFds =
mRevocableFds.valueAt(i);
- if (packageFds == null) {
+ if (rFds == null) {
continue;
}
- for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) {
- packageFds.valueAt(j).revoke();
+ for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) {
+ rFds.valueAt(j).revoke();
}
}
+ mRevocableFds.clear();
}
}
@@ -547,10 +662,10 @@
fout.println("<empty>");
} else {
for (int i = 0, count = mRevocableFds.size(); i < count; ++i) {
- final String packageName = mRevocableFds.keyAt(i);
- final ArraySet<RevocableFileDescriptor> packageFds =
+ final Accessor accessor = mRevocableFds.keyAt(i);
+ final ArraySet<RevocableFileDescriptor> rFds =
mRevocableFds.valueAt(i);
- fout.println(packageName + "#" + packageFds.size());
+ fout.println(accessor + ": #" + rFds.size());
}
}
fout.decreaseIndent();
@@ -560,7 +675,6 @@
void writeToXml(XmlSerializer out) throws IOException {
synchronized (mMetadataLock) {
XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId);
- XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId);
out.startTag(null, TAG_BLOB_HANDLE);
mBlobHandle.writeToXml(out);
@@ -584,7 +698,9 @@
static BlobMetadata createFromXml(XmlPullParser in, int version, Context context)
throws XmlPullParserException, IOException {
final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID);
- final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID);
+ if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
+ XmlUtils.readIntAttribute(in, ATTR_USER_ID);
+ }
BlobHandle blobHandle = null;
final ArraySet<Committer> committers = new ArraySet<>();
@@ -608,7 +724,7 @@
return null;
}
- final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId);
+ final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle);
blobMetadata.setCommitters(committers);
blobMetadata.setLeasees(leasees);
return blobMetadata;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 5cebf8d..502b29eb 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -27,12 +27,11 @@
import android.provider.DeviceConfig.Properties;
import android.text.TextUtils;
import android.util.DataUnit;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.TimeUtils;
-import com.android.internal.util.IndentingPrintWriter;
-
import java.io.File;
import java.util.concurrent.TimeUnit;
@@ -47,8 +46,9 @@
public static final int XML_VERSION_ADD_DESC_RES_NAME = 3;
public static final int XML_VERSION_ADD_COMMIT_TIME = 4;
public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5;
+ public static final int XML_VERSION_ALLOW_ACCESS_ACROSS_USERS = 6;
- public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
+ public static final int XML_VERSION_CURRENT = XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
public static final long INVALID_BLOB_ID = 0;
public static final long INVALID_BLOB_SIZE = 0;
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 0e73547..cc5e31a 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -32,6 +32,7 @@
import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
import static com.android.server.blob.BlobStoreConfig.LOGV;
import static com.android.server.blob.BlobStoreConfig.TAG;
+import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
@@ -83,6 +84,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.SparseArray;
@@ -96,7 +98,6 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -129,6 +130,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -146,9 +148,9 @@
@GuardedBy("mBlobsLock")
private long mCurrentMaxSessionId;
- // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
+ // Contains data of BlobHandle -> BlobMetadata.
@GuardedBy("mBlobsLock")
- private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
+ private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>();
// Contains all ids that are currently in use.
@GuardedBy("mBlobsLock")
@@ -265,16 +267,6 @@
return userSessions;
}
- @GuardedBy("mBlobsLock")
- private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
- ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
- if (userBlobs == null) {
- userBlobs = new ArrayMap<>();
- mBlobsMap.put(userId, userBlobs);
- }
- return userBlobs;
- }
-
@VisibleForTesting
void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
synchronized (mBlobsLock) {
@@ -283,9 +275,16 @@
}
@VisibleForTesting
- void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
+ BlobMetadata getBlobForTest(BlobHandle blobHandle) {
synchronized (mBlobsLock) {
- mBlobsMap.put(userId, userBlobs);
+ return mBlobsMap.get(blobHandle);
+ }
+ }
+
+ @VisibleForTesting
+ int getBlobsCountForTest() {
+ synchronized (mBlobsLock) {
+ return mBlobsMap.size();
}
}
@@ -319,14 +318,9 @@
}
@GuardedBy("mBlobsLock")
- private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
- addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
- }
-
- @GuardedBy("mBlobsLock")
- private void addBlobForUserLocked(BlobMetadata blobMetadata,
- ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
- userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
+ @VisibleForTesting
+ void addBlobLocked(BlobMetadata blobMetadata) {
+ mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata);
addActiveBlobIdLocked(blobMetadata.getBlobId());
}
@@ -404,8 +398,7 @@
private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
String callingPackage, String attributionTag) throws IOException {
synchronized (mBlobsLock) {
- final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
- .get(blobHandle);
+ final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid, attributionTag)) {
if (blobMetadata == null) {
@@ -415,7 +408,7 @@
} else {
FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
blobMetadata.getBlobId(), blobMetadata.getSize(),
- FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
+ FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED);
}
throw new SecurityException("Caller not allowed to access " + blobHandle
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
@@ -425,7 +418,7 @@
blobMetadata.getBlobId(), blobMetadata.getSize(),
FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
- return blobMetadata.openForRead(callingPackage);
+ return blobMetadata.openForRead(callingPackage, callingUid);
}
}
@@ -433,11 +426,11 @@
private int getCommittedBlobsCountLocked(int uid, String packageName) {
// TODO: Maintain a counter instead of traversing all the blobs
final AtomicInteger blobsCount = new AtomicInteger(0);
- forEachBlobInUser((blobMetadata) -> {
+ forEachBlobLocked(blobMetadata -> {
if (blobMetadata.isACommitter(packageName, uid)) {
blobsCount.getAndIncrement();
}
- }, UserHandle.getUserId(uid));
+ });
return blobsCount.get();
}
@@ -445,11 +438,11 @@
private int getLeasedBlobsCountLocked(int uid, String packageName) {
// TODO: Maintain a counter instead of traversing all the blobs
final AtomicInteger blobsCount = new AtomicInteger(0);
- forEachBlobInUser((blobMetadata) -> {
+ forEachBlobLocked(blobMetadata -> {
if (blobMetadata.isALeasee(packageName, uid)) {
blobsCount.getAndIncrement();
}
- }, UserHandle.getUserId(uid));
+ });
return blobsCount.get();
}
@@ -465,8 +458,16 @@
throw new LimitExceededException("Too many leased blobs for the caller: "
+ leasesCount);
}
- final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
- .get(blobHandle);
+ if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
+ && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
+ FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+ INVALID_BLOB_ID, INVALID_BLOB_SIZE,
+ FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
+ throw new IllegalArgumentException(
+ "Lease expiry cannot be later than blobs expiry time");
+ }
+
+ final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid, attributionTag)) {
if (blobMetadata == null) {
@@ -481,15 +482,7 @@
throw new SecurityException("Caller not allowed to access " + blobHandle
+ "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
}
- if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
- && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
- FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
- blobMetadata.getBlobId(), blobMetadata.getSize(),
- FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
- throw new IllegalArgumentException(
- "Lease expiry cannot be later than blobs expiry time");
- }
if (blobMetadata.getSize()
> getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
@@ -518,20 +511,18 @@
@GuardedBy("mBlobsLock")
long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
final AtomicLong totalBytes = new AtomicLong(0);
- forEachBlobInUser((blobMetadata) -> {
+ forEachBlobLocked((blobMetadata) -> {
if (blobMetadata.isALeasee(callingPackage, callingUid)) {
totalBytes.getAndAdd(blobMetadata.getSize());
}
- }, UserHandle.getUserId(callingUid));
+ });
return totalBytes.get();
}
private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
String callingPackage, String attributionTag) {
synchronized (mBlobsLock) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
- getUserBlobsLocked(UserHandle.getUserId(callingUid));
- final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
+ final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid, attributionTag)) {
throw new SecurityException("Caller not allowed to access " + blobHandle
@@ -547,12 +538,12 @@
synchronized (mBlobsLock) {
// Check if blobMetadata object is still valid. If it is not, then
// it means that it was already deleted and nothing else to do here.
- if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) {
+ if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) {
return;
}
if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
deleteBlobLocked(blobMetadata);
- userBlobs.remove(blobHandle);
+ mBlobsMap.remove(blobHandle);
}
writeBlobsInfoAsync();
}
@@ -583,12 +574,18 @@
}
return packageResources;
};
- getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
+ forEachBlobLocked((blobHandle, blobMetadata) -> {
+ if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) {
+ return;
+ }
final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
blobMetadata.forEachLeasee(leasee -> {
if (!leasee.isStillValid()) {
return;
}
+ if (userId != UserHandle.getUserId(leasee.uid)) {
+ return;
+ }
final int descriptionResId = leasee.descriptionResEntryName == null
? Resources.ID_NULL
: getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
@@ -608,9 +605,7 @@
private void deleteBlobInternal(long blobId, int callingUid) {
synchronized (mBlobsLock) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
- UserHandle.getUserId(callingUid));
- userBlobs.entrySet().removeIf(entry -> {
+ mBlobsMap.entrySet().removeIf(entry -> {
final BlobMetadata blobMetadata = entry.getValue();
if (blobMetadata.getBlobId() == blobId) {
deleteBlobLocked(blobMetadata);
@@ -625,19 +620,20 @@
private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
@NonNull String callingPackage) {
final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
- forEachBlobInUser(blobMetadata -> {
- if (blobMetadata.isALeasee(callingPackage, callingUid)) {
- leasedBlobs.add(blobMetadata.getBlobHandle());
- }
- }, UserHandle.getUserId(callingUid));
+ synchronized (mBlobsLock) {
+ forEachBlobLocked(blobMetadata -> {
+ if (blobMetadata.isALeasee(callingPackage, callingUid)) {
+ leasedBlobs.add(blobMetadata.getBlobHandle());
+ }
+ });
+ }
return leasedBlobs;
}
private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
int callingUid, @NonNull String callingPackage, String attributionTag) {
synchronized (mBlobsLock) {
- final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
- .get(blobHandle);
+ final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
callingPackage, callingUid, attributionTag)) {
throw new SecurityException("Caller not allowed to access " + blobHandle
@@ -699,14 +695,14 @@
FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED);
break;
}
- final int userId = UserHandle.getUserId(session.getOwnerUid());
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
- userId);
- BlobMetadata blob = userBlobs.get(session.getBlobHandle());
- if (blob == null) {
+ final BlobMetadata blob;
+ final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle());
+ if (blobIndex >= 0) {
+ blob = mBlobsMap.valueAt(blobIndex);
+ } else {
blob = new BlobMetadata(mContext, session.getSessionId(),
- session.getBlobHandle(), userId);
- addBlobForUserLocked(blob, userBlobs);
+ session.getBlobHandle());
+ addBlobLocked(blob);
}
final Committer existingCommitter = blob.getExistingCommitter(
session.getOwnerPackageName(), session.getOwnerUid());
@@ -738,7 +734,7 @@
// But if it is a recommit, just leave it as is.
if (session.getSessionId() == blob.getBlobId()) {
deleteBlobLocked(blob);
- userBlobs.remove(blob.getBlobHandle());
+ mBlobsMap.remove(blob.getBlobHandle());
}
}
// Delete redundant data from recommits.
@@ -874,13 +870,10 @@
out.startTag(null, TAG_BLOBS);
XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
- for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
- for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
- out.startTag(null, TAG_BLOB);
- userBlobs.valueAt(j).writeToXml(out);
- out.endTag(null, TAG_BLOB);
- }
+ for (int i = 0, count = mBlobsMap.size(); i < count; ++i) {
+ out.startTag(null, TAG_BLOB);
+ mBlobsMap.valueAt(i).writeToXml(out);
+ out.endTag(null, TAG_BLOB);
}
out.endTag(null, TAG_BLOBS);
@@ -925,16 +918,21 @@
if (TAG_BLOB.equals(in.getName())) {
final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
in, version, mContext);
- final SparseArray<String> userPackages = allPackages.get(
- blobMetadata.getUserId());
- if (userPackages == null) {
- blobMetadata.getBlobFile().delete();
- } else {
- addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
- blobMetadata.removeCommittersFromUnknownPkgs(userPackages);
- blobMetadata.removeLeaseesFromUnknownPkgs(userPackages);
- }
+ blobMetadata.removeCommittersFromUnknownPkgs(allPackages);
+ blobMetadata.removeLeaseesFromUnknownPkgs(allPackages);
mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
+ if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) {
+ addBlobLocked(blobMetadata);
+ } else {
+ final BlobMetadata existingBlobMetadata = mBlobsMap.get(
+ blobMetadata.getBlobHandle());
+ if (existingBlobMetadata == null) {
+ addBlobLocked(blobMetadata);
+ } else {
+ existingBlobMetadata.addCommittersAndLeasees(blobMetadata);
+ blobMetadata.getBlobFile().delete();
+ }
+ }
}
}
if (LOGV) {
@@ -977,14 +975,6 @@
}
}
- private int getPackageUid(String packageName, int userId) {
- final int uid = mPackageManagerInternal.getPackageUid(
- packageName,
- MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
- userId);
- return uid;
- }
-
private SparseArray<SparseArray<String>> getAllPackages() {
final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
@@ -1004,7 +994,7 @@
return allPackages;
}
- AtomicFile prepareSessionsIndexFile() {
+ private AtomicFile prepareSessionsIndexFile() {
final File file = BlobStoreConfig.prepareSessionIndexFile();
if (file == null) {
return null;
@@ -1012,7 +1002,7 @@
return new AtomicFile(file, "session_index" /* commitLogTag */);
}
- AtomicFile prepareBlobsIndexFile() {
+ private AtomicFile prepareBlobsIndexFile() {
final File file = BlobStoreConfig.prepareBlobsIndexFile();
if (file == null) {
return null;
@@ -1037,9 +1027,7 @@
writeBlobSessionsAsync();
// Remove the package from the committer and leasee list
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
- getUserBlobsLocked(UserHandle.getUserId(uid));
- userBlobs.entrySet().removeIf(entry -> {
+ mBlobsMap.entrySet().removeIf(entry -> {
final BlobMetadata blobMetadata = entry.getValue();
final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
if (isACommitter) {
@@ -1074,14 +1062,15 @@
}
}
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
- mBlobsMap.removeReturnOld(userId);
- if (userBlobs != null) {
- for (int i = 0, count = userBlobs.size(); i < count; ++i) {
- final BlobMetadata blobMetadata = userBlobs.valueAt(i);
+ mBlobsMap.entrySet().removeIf(entry -> {
+ final BlobMetadata blobMetadata = entry.getValue();
+ blobMetadata.removeDataForUser(userId);
+ if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
deleteBlobLocked(blobMetadata);
+ return true;
}
- }
+ return false;
+ });
if (LOGV) {
Slog.v(TAG, "Removed blobs data in user " + userId);
}
@@ -1114,22 +1103,19 @@
}
// Cleanup any stale blobs.
- for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
- userBlobs.entrySet().removeIf(entry -> {
- final BlobMetadata blobMetadata = entry.getValue();
+ mBlobsMap.entrySet().removeIf(entry -> {
+ final BlobMetadata blobMetadata = entry.getValue();
- // Remove expired leases
- blobMetadata.removeExpiredLeases();
+ // Remove expired leases
+ blobMetadata.removeExpiredLeases();
- if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
- deleteBlobLocked(blobMetadata);
- deletedBlobIds.add(blobMetadata.getBlobId());
- return true;
- }
- return false;
- });
- }
+ if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
+ deleteBlobLocked(blobMetadata);
+ deletedBlobIds.add(blobMetadata.getBlobId());
+ return true;
+ }
+ return false;
+ });
writeBlobsInfoAsync();
// Cleanup any stale sessions.
@@ -1195,34 +1181,34 @@
void runClearAllBlobs(@UserIdInt int userId) {
synchronized (mBlobsLock) {
- for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
- final int blobUserId = mBlobsMap.keyAt(i);
- if (userId != UserHandle.USER_ALL && userId != blobUserId) {
- continue;
+ mBlobsMap.entrySet().removeIf(entry -> {
+ final BlobMetadata blobMetadata = entry.getValue();
+ if (userId == UserHandle.USER_ALL) {
+ mActiveBlobIds.remove(blobMetadata.getBlobId());
+ return true;
}
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
- for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
- mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
+ blobMetadata.removeDataForUser(userId);
+ if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
+ mActiveBlobIds.remove(blobMetadata.getBlobId());
+ return true;
}
- }
- if (userId == UserHandle.USER_ALL) {
- mBlobsMap.clear();
- } else {
- mBlobsMap.remove(userId);
- }
+ return false;
+ });
writeBlobsInfoAsync();
}
}
void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
synchronized (mBlobsLock) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
- final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
+ final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle);
if (blobMetadata == null) {
return;
}
- deleteBlobLocked(blobMetadata);
- userBlobs.remove(blobHandle);
+ blobMetadata.removeDataForUser(userId);
+ if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) {
+ deleteBlobLocked(blobMetadata);
+ mBlobsMap.remove(blobHandle);
+ }
writeBlobsInfoAsync();
}
}
@@ -1235,11 +1221,12 @@
boolean isBlobAvailable(long blobId, int userId) {
synchronized (mBlobsLock) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
- for (BlobMetadata blobMetadata : userBlobs.values()) {
- if (blobMetadata.getBlobId() == blobId) {
- return true;
+ for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
+ final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
+ if (blobMetadata.getBlobId() != blobId) {
+ continue;
}
+ return blobMetadata.hasACommitterInUser(userId);
}
return false;
}
@@ -1274,27 +1261,22 @@
@GuardedBy("mBlobsLock")
private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
- for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
- final int userId = mBlobsMap.keyAt(i);
- if (!dumpArgs.shouldDumpUser(userId)) {
+ fout.println("List of blobs (" + mBlobsMap.size() + "):");
+ fout.increaseIndent();
+ for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) {
+ final BlobMetadata blobMetadata = mBlobsMap.valueAt(i);
+ if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
continue;
}
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
- fout.println("List of blobs in user #"
- + userId + " (" + userBlobs.size() + "):");
+ fout.println("Blob #" + blobMetadata.getBlobId());
fout.increaseIndent();
- for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
- final BlobMetadata blobMetadata = userBlobs.valueAt(j);
- if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
- continue;
- }
- fout.println("Blob #" + blobMetadata.getBlobId());
- fout.increaseIndent();
- blobMetadata.dump(fout, dumpArgs);
- fout.decreaseIndent();
- }
+ blobMetadata.dump(fout, dumpArgs);
fout.decreaseIndent();
}
+ if (mBlobsMap.isEmpty()) {
+ fout.println("<empty>");
+ }
+ fout.decreaseIndent();
}
private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
@@ -1308,13 +1290,12 @@
}
}, userId);
- forEachBlobInUser(blobMetadata -> {
- if (blobMetadata.isALeasee(packageName)) {
- if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
- blobsDataSize.getAndAdd(blobMetadata.getSize());
- }
+ forEachBlob(blobMetadata -> {
+ if (blobMetadata.shouldAttributeToLeasee(packageName, userId,
+ callerHasStatsPermission)) {
+ blobsDataSize.getAndAdd(blobMetadata.getSize());
}
- }, userId);
+ });
stats.dataSize += blobsDataSize.get();
}
@@ -1330,13 +1311,12 @@
}
}, userId);
- forEachBlobInUser(blobMetadata -> {
- if (blobMetadata.isALeasee(uid)) {
- if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
- blobsDataSize.getAndAdd(blobMetadata.getSize());
- }
+ forEachBlob(blobMetadata -> {
+ if (blobMetadata.shouldAttributeToLeasee(uid,
+ callerHasStatsPermission)) {
+ blobsDataSize.getAndAdd(blobMetadata.getSize());
}
- }, userId);
+ });
stats.dataSize += blobsDataSize.get();
}
@@ -1352,13 +1332,26 @@
}
}
- private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
- synchronized (mBlobsLock) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
- for (int i = 0, count = userBlobs.size(); i < count; ++i) {
- final BlobMetadata blobMetadata = userBlobs.valueAt(i);
- consumer.accept(blobMetadata);
- }
+ private void forEachBlob(Consumer<BlobMetadata> consumer) {
+ synchronized (mBlobsMap) {
+ forEachBlobLocked(consumer);
+ }
+ }
+
+ @GuardedBy("mBlobsMap")
+ private void forEachBlobLocked(Consumer<BlobMetadata> consumer) {
+ for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
+ final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
+ consumer.accept(blobMetadata);
+ }
+ }
+
+ @GuardedBy("mBlobsMap")
+ private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) {
+ for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) {
+ final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx);
+ final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx);
+ consumer.accept(blobHandle, blobMetadata);
}
}
@@ -1886,15 +1879,7 @@
}
private int pullBlobData(int atomTag, List<StatsEvent> data) {
- synchronized (mBlobsLock) {
- for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
- final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
- for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
- final BlobMetadata blob = userBlobs.valueAt(j);
- data.add(blob.dumpAsStatsEvent(atomTag));
- }
- }
- }
+ forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag)));
return StatsManager.PULL_SUCCESS;
}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 2c3f682..3f0032f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -56,12 +56,12 @@
import android.system.ErrnoException;
import android.system.Os;
import android.util.ExceptionUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.blob.BlobStoreManagerService.DumpArgs;
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 88f3df8..9ea6f79 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -208,10 +208,10 @@
public static final int FLAG_PRIORITIZE = 1 << 6;
/**
- * For apps targeting {@link Build.VERSION_CODES#S} or above, APIs
- * {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} and
- * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} will require holding a new
- * permission {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}
+ * For apps targeting {@link Build.VERSION_CODES#S} or above, any APIs setting exact alarms,
+ * e.g. {@link #setExact(int, long, PendingIntent)},
+ * {@link #setAlarmClock(AlarmClockInfo, PendingIntent)} and others will require holding a new
+ * permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM}
*
* @hide
*/
@@ -219,6 +219,21 @@
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
public static final long REQUIRE_EXACT_ALARM_PERMISSION = 171306433L;
+ /**
+ * For apps targeting {@link Build.VERSION_CODES#S} or above, all inexact alarms will require
+ * to have a minimum window size, expected to be on the order of a few minutes.
+ *
+ * Practically, any alarms requiring smaller windows are the same as exact alarms and should use
+ * the corresponding APIs provided, like {@link #setExact(int, long, PendingIntent)}, et al.
+ *
+ * Inexact alarm with shorter windows specified will have their windows elongated by the system.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
+ public static final long ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS = 185199076L;
+
@UnsupportedAppUsage
private final IAlarmManager mService;
private final Context mContext;
@@ -483,6 +498,11 @@
* modest timeliness requirements for its alarms.
*
* <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window
+ * specified is at least a few minutes, as smaller windows are considered practically exact
+ * and should use the other APIs provided for exact alarms.
+ *
+ * <p>
* This method can also be used to achieve strict ordering guarantees among
* multiple alarms by ensuring that the windows requested for each alarm do
* not intersect.
@@ -532,6 +552,13 @@
* The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
* invoked via the specified target Handler, or on the application's main looper
* if {@code null} is passed as the {@code targetHandler} parameter.
+ *
+ * <p>
+ * Note: Starting with API {@link Build.VERSION_CODES#S}, the system will ensure that the window
+ * specified is at least a few minutes, as smaller windows are considered practically exact
+ * and should use the other APIs provided for exact alarms.
+ *
+ * @see #setWindow(int, long, long, PendingIntent)
*/
public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
String tag, OnAlarmListener listener, Handler targetHandler) {
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 7a36141..03d9a96 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -452,7 +452,8 @@
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
private static final long DEFAULT_MAX_INTERVAL = 365 * INTERVAL_DAY;
- private static final long DEFAULT_MIN_WINDOW = 10_000;
+ // TODO (b/185199076): Tune based on breakage reports.
+ private static final long DEFAULT_MIN_WINDOW = 30 * 60 * 1000;
private static final long DEFAULT_ALLOW_WHILE_IDLE_WHITELIST_DURATION = 10 * 1000;
private static final long DEFAULT_LISTENER_TIMEOUT = 5 * 1000;
private static final int DEFAULT_MAX_ALARMS_PER_UID = 500;
@@ -1683,17 +1684,23 @@
}
}
- if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
- // Do not support windows for idle-until alarms.
- windowLength = AlarmManager.WINDOW_EXACT;
- }
-
- // Sanity check the window length. This will catch people mistakenly
- // trying to pass an end-of-window timestamp rather than a duration.
- if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
+ // Snap the window to reasonable limits.
+ if (windowLength > INTERVAL_DAY) {
Slog.w(TAG, "Window length " + windowLength
- + "ms suspiciously long; limiting to 1 hour");
- windowLength = AlarmManager.INTERVAL_HOUR;
+ + "ms suspiciously long; limiting to 1 day");
+ windowLength = INTERVAL_DAY;
+ } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW
+ && (flags & FLAG_PRIORITIZE) == 0) {
+ if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS,
+ callingPackage, UserHandle.getUserHandleForUid(callingUid))) {
+ Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to "
+ + mConstants.MIN_WINDOW + "ms.");
+ windowLength = mConstants.MIN_WINDOW;
+ } else {
+ // TODO (b/185199076): Remove log once we have some data about what apps will break
+ Slog.wtf(TAG, "Short window " + windowLength + "ms specified by "
+ + callingPackage);
+ }
}
// Sanity check the recurrence interval. This will catch people who supply
@@ -1730,14 +1737,13 @@
final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger;
final long maxElapsed;
- if (windowLength == AlarmManager.WINDOW_EXACT) {
+ if (windowLength == 0) {
maxElapsed = triggerElapsed;
} else if (windowLength < 0) {
maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
// Fix this window in place, so that as time approaches we don't collapse it.
windowLength = maxElapsed - triggerElapsed;
} else {
- windowLength = Math.max(windowLength, mConstants.MIN_WINDOW);
maxElapsed = triggerElapsed + windowLength;
}
synchronized (mLock) {
@@ -2135,17 +2141,63 @@
+ " does not belong to the calling uid " + callingUid);
}
- final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
- final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT);
+ // Repeating alarms must use PendingIntent, not direct listener
+ if (interval != 0 && directReceiver != null) {
+ throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers");
+ }
- // make sure the caller is allowed to use the requested kind of alarm, and also
+ if (workSource != null) {
+ getContext().enforcePermission(
+ android.Manifest.permission.UPDATE_DEVICE_STATS,
+ Binder.getCallingPid(), callingUid, "AlarmManager.set");
+ }
+
+ if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) {
+ // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
+ // manager when to come out of idle mode, which is only for DeviceIdleController.
+ if (callingUid != Process.SYSTEM_UID) {
+ // TODO (b/169463012): Throw instead of tolerating this mistake.
+ flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
+ } else {
+ // Do not support windows for idle-until alarms.
+ windowLength = 0;
+ }
+ }
+
+ // Remove flags reserved for the service, we will apply those later as appropriate.
+ flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED
+ | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+
+ // If this alarm is for an alarm clock, then it must be exact and we will
+ // use it to wake early from idle if needed.
+ if (alarmClock != null) {
+ flags |= FLAG_WAKE_FROM_IDLE;
+ windowLength = 0;
+
+ // If the caller is a core system component or on the user's allowlist, and not calling
+ // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
+ // This means we will allow these alarms to go off as normal even while idle, with no
+ // timing restrictions.
+ } else if (workSource == null && (UserHandle.isCore(callingUid)
+ || UserHandle.isSameApp(callingUid, mSystemUiUid)
+ || ((mAppStateTracker != null)
+ && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
+ flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
+ flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE);
+ }
+
+ final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0;
+ final boolean exact = (windowLength == 0);
+
+ // Make sure the caller is allowed to use the requested kind of alarm, and also
// decide what quota and broadcast options to use.
Bundle idleOptions = null;
if ((flags & FLAG_PRIORITIZE) != 0) {
getContext().enforcePermission(
Manifest.permission.SCHEDULE_PRIORITIZED_ALARM,
Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized");
- flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT);
+ // The API doesn't allow using both together.
+ flags &= ~FLAG_ALLOW_WHILE_IDLE;
} else if (exact || allowWhileIdle) {
final boolean needsPermission;
boolean lowerQuota;
@@ -2183,55 +2235,11 @@
}
}
- // Repeating alarms must use PendingIntent, not direct listener
- if (interval != 0) {
- if (directReceiver != null) {
- throw new IllegalArgumentException(
- "Repeating alarms cannot use AlarmReceivers");
- }
- }
-
- if (workSource != null) {
- getContext().enforcePermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS,
- Binder.getCallingPid(), callingUid, "AlarmManager.set");
- }
-
- // No incoming callers can request either WAKE_FROM_IDLE or
- // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate.
- flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED);
-
- // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm
- // manager when to come out of idle mode, which is only for DeviceIdleController.
- if (callingUid != Process.SYSTEM_UID) {
- flags &= ~AlarmManager.FLAG_IDLE_UNTIL;
- }
-
// If this is an exact time alarm, then it can't be batched with other alarms.
- if (windowLength == AlarmManager.WINDOW_EXACT) {
+ if (exact) {
flags |= AlarmManager.FLAG_STANDALONE;
}
- // If this alarm is for an alarm clock, then it must be standalone and we will
- // use it to wake early from idle if needed.
- if (alarmClock != null) {
- flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE;
-
- // If the caller is a core system component or on the user's whitelist, and not calling
- // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED.
- // This means we will allow these alarms to go off as normal even while idle, with no
- // timing restrictions.
- } else if (workSource == null && (UserHandle.isCore(callingUid)
- || UserHandle.isSameApp(callingUid, mSystemUiUid)
- || ((mAppStateTracker != null)
- && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) {
- flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
- flags &= ~FLAG_ALLOW_WHILE_IDLE;
- flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT;
- flags &= ~FLAG_PRIORITIZE;
- idleOptions = null;
- }
-
setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver,
listenerTag, flags, workSource, alarmClock, callingUid, callingPackage,
idleOptions);
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
index c37d2c3..9b1b066 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java
@@ -16,7 +16,6 @@
package com.android.server.alarm;
-import static com.android.server.alarm.AlarmManagerService.TAG;
import static com.android.server.alarm.AlarmManagerService.dumpAlarmList;
import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index d94d638..131783f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -16,6 +16,7 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.IntDef;
@@ -39,7 +40,9 @@
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
+import android.util.Pools;
import android.util.Slog;
+import android.util.SparseArrayMap;
import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
@@ -60,6 +63,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Iterator;
import java.util.List;
+import java.util.function.Consumer;
/**
* This class decides, given the various configuration and the system status, which jobs can start
@@ -73,6 +77,12 @@
private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS =
CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms";
private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000;
+ private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej";
+ private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3;
+ private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR =
+ CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular";
+ private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2;
/**
* Set of possible execution types that a job can have. The actual type(s) of a job are based
@@ -165,8 +175,6 @@
private long mLastScreenOnRealtime;
private long mLastScreenOffRealtime;
- private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT;
-
private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON =
new WorkConfigLimitsPerMemoryTrimLevel(
new WorkTypeConfig("screen_on_normal", 11,
@@ -274,11 +282,28 @@
private final WorkCountTracker mWorkCountTracker = new WorkCountTracker();
+ private final Pools.Pool<PackageStats> mPkgStatsPool =
+ new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT);
+
+ private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>();
+
private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal;
/** Wait for this long after screen off before adjusting the job concurrency. */
private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS;
+ /**
+ * The maximum number of expedited jobs a single userId-package can have running simultaneously.
+ * TOP apps are not limited.
+ */
+ private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ;
+
+ /**
+ * The maximum number of regular jobs a single userId-package can have running simultaneously.
+ * TOP apps are not limited.
+ */
+ private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR;
+
/** Current memory trim level. */
private int mLastMemoryTrimLevel;
@@ -286,6 +311,9 @@
private long mNextSystemStateRefreshTime;
private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000;
+ private final Consumer<PackageStats> mPackageStatsStagingCountClearer =
+ PackageStats::resetStagedCount;
+
private final StatLogger mStatLogger = new StatLogger(new String[]{
"assignJobsToContexts",
"refreshSystemState",
@@ -330,6 +358,21 @@
onInteractiveStateChanged(mPowerManager.isInteractive());
}
+ @GuardedBy("mLock")
+ void onAppRemovedLocked(String pkgName, int uid) {
+ final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName);
+ if (packageStats != null) {
+ if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) {
+ // Don't delete the object just yet. We'll remove it in onJobCompleted() when the
+ // jobs officially stop running.
+ Slog.w(TAG,
+ pkgName + "(" + uid + ") marked as removed before jobs stopped running");
+ } else {
+ mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName);
+ }
+ }
+ }
+
void onUserRemoved(int userId) {
mGracePeriodObserver.onUserRemoved(userId);
}
@@ -557,6 +600,7 @@
boolean startingJob = false;
int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED;
String preemptReason = null;
+ final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending);
// TODO(141645789): rewrite this to look at empty contexts first so we don't
// unnecessarily preempt
for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) {
@@ -566,7 +610,7 @@
final boolean preferredUidOkay = (preferredUid == nextPending.getUid())
|| (preferredUid == JobServiceContext.NO_PREFERRED_UID);
- if (preferredUidOkay && workType != WORK_TYPE_NONE) {
+ if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) {
// This slot is free, and we haven't yet hit the limit on
// concurrent jobs... we can just throw the job in to here.
selectedContextId = j;
@@ -579,9 +623,11 @@
continue;
}
if (job.getUid() != nextPending.getUid()) {
- // Maybe stop the job if it has had its day in the sun.
+ // Maybe stop the job if it has had its day in the sun. Don't let a different
+ // app preempt jobs started for TOP apps though.
final String reason = shouldStopJobReason[j];
- if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
+ if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP
+ && reason != null && mWorkCountTracker.canJobStart(allWorkTypes,
activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) {
// Right now, the way the code is set up, we don't need to explicitly
// assign the new job to this context since we'll reassign when the
@@ -608,23 +654,27 @@
// actually starting a job, so don't set startingJob.
}
}
+ final PackageStats packageStats = getPkgStatsLocked(
+ nextPending.getSourceUserId(), nextPending.getSourcePackageName());
if (selectedContextId != -1) {
contextIdToJobMap[selectedContextId] = nextPending;
slotChanged[selectedContextId] = true;
preemptReasonCodeForContext[selectedContextId] = preemptReasonCode;
preemptReasonForContext[selectedContextId] = preemptReason;
+ packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob());
}
if (startingJob) {
// Increase the counters when we're going to start a job.
workTypeForContext[selectedContextId] = workType;
mWorkCountTracker.stageJob(workType, allWorkTypes);
+ mActivePkgStats.add(
+ nextPending.getSourceUserId(), nextPending.getSourcePackageName(),
+ packageStats);
}
}
if (DEBUG) {
Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final"));
- }
- if (DEBUG) {
Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString());
}
@@ -660,6 +710,7 @@
}
}
mWorkCountTracker.resetStagingCount();
+ mActivePkgStats.forEach(mPackageStatsStagingCountClearer);
noteConcurrency();
}
@@ -702,18 +753,66 @@
}
@GuardedBy("mLock")
+ @NonNull
+ private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) {
+ PackageStats packageStats = mActivePkgStats.get(userId, packageName);
+ if (packageStats == null) {
+ packageStats = mPkgStatsPool.acquire();
+ if (packageStats == null) {
+ packageStats = new PackageStats();
+ }
+ packageStats.setPackage(userId, packageName);
+ }
+ return packageStats;
+ }
+
+ @GuardedBy("mLock")
+ private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) {
+ if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
+ // Don't restrict top apps' concurrency. The work type limits will make sure
+ // background jobs have slots to run if the system has resources.
+ return false;
+ }
+ // Use < instead of <= as that gives us a little wiggle room in case a new job comes
+ // along very shortly.
+ if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) {
+ // Don't artificially limit a single package if we don't even have enough jobs to use
+ // the maximum number of slots. We'll preempt the job later if we need the slot.
+ return false;
+ }
+ final PackageStats packageStats =
+ mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ if (packageStats == null) {
+ // No currently running jobs.
+ return false;
+ }
+ if (jobStatus.shouldTreatAsExpeditedJob()) {
+ return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj;
+ } else {
+ return packageStats.numRunningRegular + packageStats.numStagedRegular
+ < mPkgConcurrencyLimitRegular;
+ }
+ }
+
+ @GuardedBy("mLock")
private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus,
@WorkType final int workType) {
final List<StateController> controllers = mService.mControllers;
for (int ic = 0; ic < controllers.size(); ic++) {
controllers.get(ic).prepareForExecutionLocked(jobStatus);
}
+ final PackageStats packageStats =
+ getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob());
if (!worker.executeRunnableJob(jobStatus, workType)) {
Slog.e(TAG, "Error executing " + jobStatus);
mWorkCountTracker.onStagedJobFailed(workType);
} else {
mRunningJobs.add(jobStatus);
mWorkCountTracker.onJobStarted(workType);
+ packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob());
+ mActivePkgStats.add(
+ jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats);
}
final List<JobStatus> pendingJobs = mService.mPendingJobs;
if (pendingJobs.remove(jobStatus)) {
@@ -726,6 +825,18 @@
@WorkType final int workType) {
mWorkCountTracker.onJobFinished(workType);
mRunningJobs.remove(jobStatus);
+ final PackageStats packageStats =
+ mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName());
+ if (packageStats == null) {
+ Slog.wtf(TAG, "Running job didn't have an active PackageStats object");
+ } else {
+ packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob);
+ if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) {
+ mActivePkgStats.delete(packageStats.userId, packageStats.packageName);
+ mPkgStatsPool.release(packageStats);
+ }
+ }
+
final List<JobStatus> pendingJobs = mService.mPendingJobs;
if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) {
updateCounterConfigLocked();
@@ -746,7 +857,7 @@
}
if (worker.getPreferredUid() != nextPending.getUid()) {
- if (backupJob == null) {
+ if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) {
int allWorkTypes = getJobWorkTypes(nextPending);
int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType != WORK_TYPE_NONE) {
@@ -758,6 +869,13 @@
continue;
}
+ // Only bypass the concurrent limit if we had preempted the job due to a higher
+ // priority job.
+ if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority
+ && isPkgConcurrencyLimitedLocked(nextPending)) {
+ continue;
+ }
+
if (highestPriorityJob == null
|| highestPriorityJob.lastEvaluatedPriority
< nextPending.lastEvaluatedPriority) {
@@ -815,6 +933,10 @@
continue;
}
+ if (isPkgConcurrencyLimitedLocked(nextPending)) {
+ continue;
+ }
+
final int allWorkTypes = getJobWorkTypes(nextPending);
final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes);
if (workAsType == WORK_TYPE_NONE) {
@@ -979,8 +1101,16 @@
CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties);
CONFIG_LIMITS_SCREEN_OFF.low.update(properties);
CONFIG_LIMITS_SCREEN_OFF.critical.update(properties);
+
+ // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT].
+ mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+ properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ)));
+ mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT,
+ properties.getInt(
+ KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR)));
}
+ @GuardedBy("mLock")
public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) {
pw.println("Concurrency:");
@@ -989,6 +1119,8 @@
pw.println("Configuration:");
pw.increaseIndent();
pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println();
+ pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println();
+ pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println();
pw.println();
CONFIG_LIMITS_SCREEN_ON.normal.dump(pw);
pw.println();
@@ -1033,6 +1165,12 @@
pw.println(mLastMemoryTrimLevel);
pw.println();
+ pw.println("Active Package stats:");
+ pw.increaseIndent();
+ mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw));
+ pw.decreaseIndent();
+ pw.println();
+
pw.print("User Grace Period: ");
pw.println(mGracePeriodObserver.mGracePeriodExpiration);
pw.println();
@@ -1620,4 +1758,53 @@
return sb.toString();
}
}
+
+ private static class PackageStats {
+ public int userId;
+ public String packageName;
+ public int numRunningEj;
+ public int numRunningRegular;
+ public int numStagedEj;
+ public int numStagedRegular;
+
+ private void setPackage(int userId, @NonNull String packageName) {
+ this.userId = userId;
+ this.packageName = packageName;
+ numRunningEj = numRunningRegular = 0;
+ resetStagedCount();
+ }
+
+ private void resetStagedCount() {
+ numStagedEj = numStagedRegular = 0;
+ }
+
+ private void adjustRunningCount(boolean add, boolean forEj) {
+ if (forEj) {
+ numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1));
+ } else {
+ numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1));
+ }
+ }
+
+ private void adjustStagedCount(boolean add, boolean forEj) {
+ if (forEj) {
+ numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1));
+ } else {
+ numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1));
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void dumpLocked(IndentingPrintWriter pw) {
+ pw.print("PackageStats{");
+ pw.print(userId);
+ pw.print("-");
+ pw.print(packageName);
+ pw.print("#runEJ", numRunningEj);
+ pw.print("#runReg", numRunningRegular);
+ pw.print("#stagedEJ", numStagedEj);
+ pw.print("#stagedReg", numStagedRegular);
+ pw.println("}");
+ }
+ }
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d72f565b..1815661 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -816,6 +816,7 @@
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
}
mDebuggableApps.remove(pkgName);
+ mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid);
}
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
@@ -2233,7 +2234,8 @@
}
/** Returns true if both the calling and source users for the job are started. */
- private boolean areUsersStartedLocked(final JobStatus job) {
+ @GuardedBy("mLock")
+ public boolean areUsersStartedLocked(final JobStatus job) {
boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId());
if (job.getUserId() == job.getSourceUserId()) {
return sourceStarted;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 999c53f..12d9c7f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -34,6 +34,7 @@
import android.util.SparseArrayMap;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.annotations.GuardedBy;
import com.android.server.job.JobSchedulerService;
import java.util.Objects;
@@ -58,13 +59,28 @@
return;
}
switch (action) {
+ case Intent.ACTION_PACKAGE_ADDED:
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ // Only do this for app updates since new installs won't have any jobs
+ // scheduled.
+ final Uri uri = intent.getData();
+ final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
+ if (pkg != null) {
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final int userId = UserHandle.getUserId(pkgUid);
+ updateComponentStateForPackage(userId, pkg);
+ }
+ }
+ break;
case Intent.ACTION_PACKAGE_CHANGED:
final Uri uri = intent.getData();
final String pkg = uri != null ? uri.getSchemeSpecificPart() : null;
final String[] changedComponents = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
if (pkg != null && changedComponents != null && changedComponents.length > 0) {
- updateComponentStateForPackage(pkg);
+ final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ final int userId = UserHandle.getUserId(pkgUid);
+ updateComponentStateForPackage(userId, pkg);
}
break;
case Intent.ACTION_USER_UNLOCKED:
@@ -86,6 +102,7 @@
super(service);
final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(
@@ -99,6 +116,7 @@
}
@Override
+ @GuardedBy("mLock")
public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
updateComponentEnabledStateLocked(jobStatus);
}
@@ -108,30 +126,53 @@
boolean forUpdate) {
}
+ @Override
+ @GuardedBy("mLock")
+ public void onAppRemovedLocked(String packageName, int uid) {
+ clearComponentsForPackageLocked(UserHandle.getUserId(uid), packageName);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ public void onUserRemovedLocked(int userId) {
+ mServiceInfoCache.delete(userId);
+ }
+
@Nullable
- private ServiceInfo getServiceInfo(JobStatus jobStatus) {
+ @GuardedBy("mLock")
+ private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) {
final ComponentName service = jobStatus.getServiceComponent();
final int userId = jobStatus.getUserId();
- ServiceInfo si = mServiceInfoCache.get(userId, service);
- if (si == null) {
- try {
- // createContextAsUser may potentially be expensive
- // TODO: cache user context or improve ContextImpl implementation if this becomes
- // a problem
- si = mContext.createContextAsUser(UserHandle.of(userId), 0)
- .getPackageManager()
- .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
- return null;
- }
- mServiceInfoCache.add(userId, service, si);
+ if (mServiceInfoCache.contains(userId, service)) {
+ // Return whatever is in the cache, even if it's null. When something changes, we
+ // clear the cache.
+ return mServiceInfoCache.get(userId, service);
}
+
+ ServiceInfo si;
+ try {
+ // createContextAsUser may potentially be expensive
+ // TODO: cache user context or improve ContextImpl implementation if this becomes
+ // a problem
+ si = mContext.createContextAsUser(UserHandle.of(userId), 0)
+ .getPackageManager()
+ .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO);
+ } catch (NameNotFoundException e) {
+ if (mService.areUsersStartedLocked(jobStatus)) {
+ // User is fully unlocked but PM still says the package doesn't exist.
+ Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName());
+ }
+ // Write null to the cache so we don't keep querying PM.
+ si = null;
+ }
+ mServiceInfoCache.add(userId, service, si);
+
return si;
}
+ @GuardedBy("mLock")
private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) {
- final ServiceInfo service = getServiceInfo(jobStatus);
+ final ServiceInfo service = getServiceInfoLocked(jobStatus);
if (DEBUG && service == null) {
Slog.v(TAG, jobStatus.toShortString() + " component not present");
@@ -141,20 +182,26 @@
return !Objects.equals(ogService, service);
}
- private void updateComponentStateForPackage(final String pkg) {
- synchronized (mLock) {
- for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) {
- final int userId = mServiceInfoCache.keyAt(u);
-
- for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
- final ComponentName cn = mServiceInfoCache.keyAt(u, c);
- if (cn.getPackageName().equals(pkg)) {
- mServiceInfoCache.delete(userId, cn);
- }
- }
+ @GuardedBy("mLock")
+ private void clearComponentsForPackageLocked(final int userId, final String pkg) {
+ final int uIdx = mServiceInfoCache.indexOfKey(userId);
+ for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) {
+ final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c);
+ if (cn.getPackageName().equals(pkg)) {
+ mServiceInfoCache.delete(userId, cn);
}
- updateComponentStatesLocked(
- jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg));
+ }
+ }
+
+ private void updateComponentStateForPackage(final int userId, final String pkg) {
+ synchronized (mLock) {
+ clearComponentsForPackageLocked(userId, pkg);
+ updateComponentStatesLocked(jobStatus -> {
+ // Using user ID instead of source user ID because the service will run under the
+ // user ID, not source user ID.
+ return jobStatus.getUserId() == userId
+ && jobStatus.getServiceComponent().getPackageName().equals(pkg);
+ });
}
}
@@ -169,6 +216,7 @@
}
}
+ @GuardedBy("mLock")
private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) {
mComponentStateUpdateFunctor.reset();
mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor);
@@ -178,24 +226,40 @@
}
final class ComponentStateUpdateFunctor implements Consumer<JobStatus> {
+ @GuardedBy("mLock")
boolean mChanged;
@Override
+ @GuardedBy("mLock")
public void accept(JobStatus jobStatus) {
mChanged |= updateComponentEnabledStateLocked(jobStatus);
}
+ @GuardedBy("mLock")
private void reset() {
mChanged = false;
}
}
@Override
+ @GuardedBy("mLock")
public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) {
-
+ for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) {
+ final int userId = mServiceInfoCache.keyAt(u);
+ for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) {
+ final ComponentName componentName = mServiceInfoCache.keyAt(u, p);
+ pw.print(userId);
+ pw.print("-");
+ pw.print(componentName);
+ pw.print(": ");
+ pw.print(mServiceInfoCache.valueAt(u, p));
+ pw.println();
+ }
+ }
}
@Override
+ @GuardedBy("mLock")
public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
Predicate<JobStatus> predicate) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 11a8b3b..500735b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -83,6 +83,7 @@
* instance.
*/
private static final long MIN_STATS_UPDATE_INTERVAL_MS = 30_000L;
+ private static final long MIN_ADJUST_CALLBACK_INTERVAL_MS = 1_000L;
private static final int UNBYPASSABLE_BG_BLOCKED_REASONS =
~ConnectivityManager.BLOCKED_REASON_NONE;
@@ -210,6 +211,7 @@
* is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
*/
private final List<UidStats> mSortedStats = new ArrayList<>();
+ private long mLastCallbackAdjustmentTimeElapsed;
private static final int MSG_ADJUST_CALLBACKS = 0;
@@ -693,7 +695,11 @@
}
private void postAdjustCallbacks() {
- mHandler.obtainMessage(MSG_ADJUST_CALLBACKS).sendToTarget();
+ postAdjustCallbacks(0);
+ }
+
+ private void postAdjustCallbacks(long delayMs) {
+ mHandler.sendEmptyMessageDelayed(MSG_ADJUST_CALLBACKS, delayMs);
}
@GuardedBy("mLock")
@@ -708,6 +714,12 @@
}
final long nowElapsed = sElapsedRealtimeClock.millis();
+ if (nowElapsed - mLastCallbackAdjustmentTimeElapsed < MIN_ADJUST_CALLBACK_INTERVAL_MS) {
+ postAdjustCallbacks(MIN_ADJUST_CALLBACK_INTERVAL_MS);
+ return;
+ }
+
+ mLastCallbackAdjustmentTimeElapsed = nowElapsed;
mSortedStats.clear();
for (int u = 0; u < mUidStats.size(); ++u) {
@@ -926,7 +938,10 @@
UidDefaultNetworkCallback defaultNetworkCallback =
mCurrentDefaultNetworkCallbacks.get(jobs.valueAt(0).getSourceUid());
if (defaultNetworkCallback == null) {
- maybeRegisterDefaultNetworkCallbackLocked(jobs.valueAt(0));
+ // This method is only called via a network callback object. That means something
+ // changed about a general network characteristic (since we wouldn't be in this
+ // situation if called from a UID_specific callback). The general network callback
+ // will handle adjusting the per-UID callbacks, so nothing left to do here.
return false;
}
@@ -1106,8 +1121,13 @@
synchronized (mLock) {
if (Objects.equals(mDefaultNetwork, network)) {
mDefaultNetwork = null;
+ updateTrackedJobsLocked(mUid, network);
+ // Add a delay in case onAvailable()+onBlockedStatusChanged is called for a
+ // new network. If this onLost was called because the network is completely
+ // gone, the delay will hel make sure we don't have a short burst of adjusting
+ // callback calls.
+ postAdjustCallbacks(1000);
}
- updateTrackedJobsLocked(mUid, network);
}
}
diff --git a/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt
index bebf019..b7d7ed8 100644
--- a/apex/media/framework/api/current.txt
+++ b/apex/media/framework/api/current.txt
@@ -10,7 +10,6 @@
method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes();
method public boolean isFormatSpecified(@NonNull String);
method public boolean isHdrTypeSupported(@NonNull String);
- method public boolean isSlowMotionSupported();
method public boolean isVideoMimeTypeSupported(@NonNull String);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR;
diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
index 3f30d3e..97fa0ec 100644
--- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
+++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java
@@ -242,7 +242,7 @@
}
};
- /*
+ /**
* Query the video codec mime types supported by the application.
* @return List of supported video codec mime types. The list will be empty if there are none.
*/
@@ -251,7 +251,7 @@
return new ArrayList<>(mSupportedVideoMimeTypes);
}
- /*
+ /**
* Query the video codec mime types that are not supported by the application.
* @return List of unsupported video codec mime types. The list will be empty if there are none.
*/
@@ -260,7 +260,7 @@
return new ArrayList<>(mUnsupportedVideoMimeTypes);
}
- /*
+ /**
* Query all hdr types that are supported by the application.
* @return List of supported hdr types. The list will be empty if there are none.
*/
@@ -269,7 +269,7 @@
return new ArrayList<>(mSupportedHdrTypes);
}
- /*
+ /**
* Query all hdr types that are not supported by the application.
* @return List of unsupported hdr types. The list will be empty if there are none.
*/
@@ -278,7 +278,7 @@
return new ArrayList<>(mUnsupportedHdrTypes);
}
- /*
+ /**
* Whether handling of slow-motion video is supported
* @hide
*/
diff --git a/boot/hiddenapi/OWNERS b/boot/hiddenapi/OWNERS
index 5d869fc..74a3dc0 100644
--- a/boot/hiddenapi/OWNERS
+++ b/boot/hiddenapi/OWNERS
@@ -1,7 +1,5 @@
# compat-team@ for changes to hiddenapi files
-andreionea@google.com
-mathewi@google.com
-satayev@google.com
+file:tools/platform-compat:/OWNERS
# Escalations:
per-file hiddenapi-* = bdc@google.com, narayan@google.com
diff --git a/core/api/current.txt b/core/api/current.txt
index 4b12d54..0420714 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8604,37 +8604,37 @@
public final class BluetoothA2dp implements android.bluetooth.BluetoothProfile {
method public void finalize();
- method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(android.bluetooth.BluetoothDevice);
- method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
- method public boolean isA2dpPlaying(android.bluetooth.BluetoothDevice);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
- field public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isA2dpPlaying(android.bluetooth.BluetoothDevice);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
field public static final int STATE_NOT_PLAYING = 11; // 0xb
field public static final int STATE_PLAYING = 10; // 0xa
}
public final class BluetoothAdapter {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelDiscovery();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean cancelDiscovery();
method public static boolean checkBluetoothAddress(String);
method public void closeProfileProxy(int, android.bluetooth.BluetoothProfile);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disable();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enable();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getAddress();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enable();
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, "android.permission.LOCAL_MAC_ADDRESS"}) public String getAddress();
method public android.bluetooth.le.BluetoothLeAdvertiser getBluetoothLeAdvertiser();
method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
method public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
method public int getLeMaximumAdvertisingDataLength();
- method public String getName();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getProfileConnectionState(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
method public boolean getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int);
method public android.bluetooth.BluetoothDevice getRemoteDevice(String);
method public android.bluetooth.BluetoothDevice getRemoteDevice(byte[]);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getScanMode();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getState();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isDiscovering();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEnabled();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getScanMode();
+ method public int getState();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
+ method public boolean isEnabled();
method public boolean isLe2MPhySupported();
method public boolean isLeCodedPhySupported();
method public boolean isLeExtendedAdvertisingSupported();
@@ -8642,22 +8642,22 @@
method public boolean isMultipleAdvertisementSupported();
method public boolean isOffloadedFilteringSupported();
method public boolean isOffloadedScanBatchingSupported();
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingInsecureL2capChannel() throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setName(String);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startDiscovery();
- method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
- method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
- method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
- field public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
- field public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
- field public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
- field public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
- field public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
- field public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureL2capChannel() throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingL2capChannel() throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(String, java.util.UUID) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setName(String);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
field public static final String ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
field public static final int ERROR = -2147483648; // 0x80000000
field public static final String EXTRA_CONNECTION_STATE = "android.bluetooth.adapter.extra.CONNECTION_STATE";
@@ -9007,38 +9007,38 @@
}
public final class BluetoothDevice implements android.os.Parcelable {
- method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
- method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
- method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
- method public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean createBond();
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createInsecureL2capChannel(int) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createL2capChannel(int) throws java.io.IOException;
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int, int, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond();
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureL2capChannel(int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createInsecureRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createL2capChannel(int) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothSocket createRfcommSocketToServiceRecord(java.util.UUID) throws java.io.IOException;
method public int describeContents();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean fetchUuidsWithSdp();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean fetchUuidsWithSdp();
method public String getAddress();
- method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getAlias();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.bluetooth.BluetoothClass getBluetoothClass();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getBondState();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public String getName();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getType();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public android.os.ParcelUuid[] getUuids();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String);
+ method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getAlias();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothClass getBluetoothClass();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getBondState();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getType();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelUuid[] getUuids();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setAlias(@NonNull String);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPairingConfirmation(boolean);
- method public boolean setPin(byte[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(byte[]);
method public void writeToParcel(android.os.Parcel, int);
- field public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
- field public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
- field public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
- field public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
- field public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED";
- field public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED";
- field public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND";
- field public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
- field public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
- field public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public static final String ACTION_UUID = "android.bluetooth.device.action.UUID";
field public static final int ADDRESS_TYPE_PUBLIC = 0; // 0x0
field public static final int ADDRESS_TYPE_RANDOM = 1; // 0x1
field public static final int BOND_BONDED = 12; // 0xc
@@ -9076,30 +9076,30 @@
}
public final class BluetoothGatt implements android.bluetooth.BluetoothProfile {
- method public void abortReliableWrite();
- method @Deprecated public void abortReliableWrite(android.bluetooth.BluetoothDevice);
- method public boolean beginReliableWrite();
- method public void close();
- method public boolean connect();
- method public void disconnect();
- method public boolean discoverServices();
- method public boolean executeReliableWrite();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean beginReliableWrite();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method public int getConnectionState(android.bluetooth.BluetoothDevice);
method public android.bluetooth.BluetoothDevice getDevice();
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
- method public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
- method public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
- method public void readPhy();
- method public boolean readRemoteRssi();
- method public boolean requestConnectionPriority(int);
- method public boolean requestMtu(int);
- method public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
- method public void setPreferredPhy(int, int, int);
- method public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
- method public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readDescriptor(android.bluetooth.BluetoothGattDescriptor);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readRemoteRssi();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestConnectionPriority(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int, int, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
field public static final int CONNECTION_PRIORITY_HIGH = 1; // 0x1
field public static final int CONNECTION_PRIORITY_LOW_POWER = 2; // 0x2
@@ -9209,21 +9209,21 @@
}
public final class BluetoothGattServer implements android.bluetooth.BluetoothProfile {
- method public boolean addService(android.bluetooth.BluetoothGattService);
- method public void cancelConnection(android.bluetooth.BluetoothDevice);
- method public void clearServices();
- method public void close();
- method public boolean connect(android.bluetooth.BluetoothDevice, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean addService(android.bluetooth.BluetoothGattService);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void cancelConnection(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void clearServices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice, boolean);
method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method public int getConnectionState(android.bluetooth.BluetoothDevice);
method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
- method public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
- method public void readPhy(android.bluetooth.BluetoothDevice);
- method public boolean removeService(android.bluetooth.BluetoothGattService);
- method public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
- method public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(android.bluetooth.BluetoothDevice, int, int, int);
}
public abstract class BluetoothGattServerCallback {
@@ -9261,18 +9261,18 @@
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
- method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(android.bluetooth.BluetoothDevice);
- method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
- method public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
- method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
- method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
- method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
- field public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
- field public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
field public static final int AT_CMD_TYPE_ACTION = 4; // 0x4
field public static final int AT_CMD_TYPE_BASIC = 3; // 0x3
field public static final int AT_CMD_TYPE_READ = 0; // 0x0
@@ -9289,14 +9289,14 @@
}
@Deprecated public final class BluetoothHealth implements android.bluetooth.BluetoothProfile {
- method @Deprecated public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
- method @Deprecated public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
- method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
- method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
- method @Deprecated public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
- method @Deprecated public boolean registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback);
- method @Deprecated public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.os.ParcelFileDescriptor getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration);
field @Deprecated public static final int APP_CONFIG_REGISTRATION_FAILURE = 1; // 0x1
field @Deprecated public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0; // 0x0
field @Deprecated public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3; // 0x3
@@ -9327,24 +9327,24 @@
}
public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
- method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
- method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
- method public boolean connect(android.bluetooth.BluetoothDevice);
- method public boolean disconnect(android.bluetooth.BluetoothDevice);
- method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method public int getConnectionState(android.bluetooth.BluetoothDevice);
- method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
- method public boolean registerApp(android.bluetooth.BluetoothHidDeviceAppSdpSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, java.util.concurrent.Executor, android.bluetooth.BluetoothHidDevice.Callback);
- method public boolean replyReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
- method public boolean reportError(android.bluetooth.BluetoothDevice, byte);
- method public boolean sendReport(android.bluetooth.BluetoothDevice, int, byte[]);
- method public boolean unregisterApp();
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerApp(android.bluetooth.BluetoothHidDeviceAppSdpSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, android.bluetooth.BluetoothHidDeviceAppQosSettings, java.util.concurrent.Executor, android.bluetooth.BluetoothHidDevice.Callback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean replyReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean reportError(android.bluetooth.BluetoothDevice, byte);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendReport(android.bluetooth.BluetoothDevice, int, byte[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterApp();
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
field public static final byte ERROR_RSP_INVALID_PARAM = 4; // 0x4
field public static final byte ERROR_RSP_INVALID_RPT_ID = 2; // 0x2
field public static final byte ERROR_RSP_NOT_READY = 1; // 0x1
@@ -9410,26 +9410,26 @@
}
public final class BluetoothLeAudio implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
- method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
- method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
- field public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
+ method public void close();
+ method protected void finalize();
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
}
public final class BluetoothManager {
method public android.bluetooth.BluetoothAdapter getAdapter();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(android.bluetooth.BluetoothDevice, int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]);
- method public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int, int[]);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGattServer openGattServer(android.content.Context, android.bluetooth.BluetoothGattServerCallback);
}
public interface BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionState(android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
field public static final int A2DP = 2; // 0x2
field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE";
field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
@@ -9460,7 +9460,7 @@
public final class BluetoothSocket implements java.io.Closeable {
method public void close() throws java.io.IOException;
- method public void connect() throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect() throws java.io.IOException;
method public int getConnectionType();
method public java.io.InputStream getInputStream() throws java.io.IOException;
method public int getMaxReceivePacketSize();
@@ -9538,13 +9538,13 @@
}
public final class AdvertisingSet {
- method public void enableAdvertising(boolean, int, int);
- method public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
- method public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
- method public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
- method public void setPeriodicAdvertisingEnabled(boolean);
- method public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
- method public void setScanResponseData(android.bluetooth.le.AdvertiseData);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void enableAdvertising(boolean, int, int);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingParameters(android.bluetooth.le.AdvertisingSetParameters);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingData(android.bluetooth.le.AdvertiseData);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingParameters(android.bluetooth.le.PeriodicAdvertisingParameters);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setScanResponseData(android.bluetooth.le.AdvertiseData);
}
public abstract class AdvertisingSetCallback {
@@ -9607,23 +9607,23 @@
}
public final class BluetoothLeAdvertiser {
- method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
- method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
- method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
- method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
- method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback);
- method public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
- method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
- method public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT}) public void startAdvertisingSet(android.bluetooth.le.AdvertisingSetParameters, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.PeriodicAdvertisingParameters, android.bluetooth.le.AdvertiseData, int, int, android.bluetooth.le.AdvertisingSetCallback, android.os.Handler);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertisingSet(android.bluetooth.le.AdvertisingSetCallback);
}
public final class BluetoothLeScanner {
- method public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void startScan(android.bluetooth.le.ScanCallback);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public int startScan(@Nullable java.util.List<android.bluetooth.le.ScanFilter>, @Nullable android.bluetooth.le.ScanSettings, @NonNull android.app.PendingIntent);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopScan(android.bluetooth.le.ScanCallback);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public void stopScan(android.app.PendingIntent);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int startScan(@Nullable java.util.List<android.bluetooth.le.ScanFilter>, @Nullable android.bluetooth.le.ScanSettings, @NonNull android.app.PendingIntent);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(android.app.PendingIntent);
field public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";
field public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
field public static final String EXTRA_LIST_SCAN_RESULT = "android.bluetooth.le.extra.LIST_SCAN_RESULT";
@@ -18824,6 +18824,7 @@
package android.hardware.display {
public final class DeviceProductInfo implements android.os.Parcelable {
+ ctor public DeviceProductInfo(@Nullable String, @NonNull String, @NonNull String, @IntRange(from=1990) int, int);
method public int describeContents();
method public int getConnectionToSinkType();
method @IntRange(from=0xffffffff, to=53) public int getManufactureWeek();
@@ -20379,7 +20380,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 int getEncodedSurroundMode();
method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
method public int getMode();
method public String getParameters(String);
@@ -20402,7 +20403,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 isSurroundFormatEnabled(int);
method public boolean isVolumeFixed();
method @Deprecated public boolean isWiredHeadsetOn();
method public void loadSoundEffects();
@@ -25217,7 +25218,6 @@
method public float getPlaybackSpeed();
method public long getPosition();
method public int getState();
- method public boolean isActive();
method public void writeToParcel(android.os.Parcel, int);
field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
field public static final long ACTION_PAUSE = 2L; // 0x2L
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 2497827..b653410 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -161,6 +161,10 @@
method public void onVolumeChanged(@NonNull android.media.session.MediaSession.Token, int);
}
+ public final class PlaybackState implements android.os.Parcelable {
+ method public boolean isActiveState();
+ }
+
}
package android.net {
@@ -232,7 +236,7 @@
}
public class VpnManager {
- field @Deprecated public static final int TYPE_VPN_LEGACY = 3; // 0x3
+ field public static final int TYPE_VPN_LEGACY = 3; // 0x3
field public static final int TYPE_VPN_NONE = -1; // 0xffffffff
field public static final int TYPE_VPN_OEM = 4; // 0x4
field public static final int TYPE_VPN_PLATFORM = 2; // 0x2
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 37bc44f..f7b4cdc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1901,7 +1901,7 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getDynamicBufferSupport();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setBufferLengthMillis(int, int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1; // 0x1
field public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; // 0x2
field public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0; // 0x0
@@ -1917,22 +1917,22 @@
method public void finalize();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isAudioPlaying(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothAdapter {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean addOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
- method public boolean disableBLE();
- method public boolean enableBLE();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean enableNoAutoConnect();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableNoAutoConnect();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void generateLocalOobData(int, @NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.OobDataCallback);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getDiscoveryEndMillis();
method public boolean isBleScanAlwaysAvailable();
method public boolean isLeEnabled();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeActiveDevice(int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeOnMetadataChangedListener(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothAdapter.OnMetadataChangedListener);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setActiveDevice(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
field public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE = "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
field public static final int ACTIVE_DEVICE_ALL = 2; // 0x2
@@ -1948,18 +1948,20 @@
}
public static interface BluetoothAdapter.OobDataCallback {
+ method public void onError(int);
+ method public void onOobData(int, @Nullable android.bluetooth.OobData);
}
public final class BluetoothDevice implements android.os.Parcelable {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean canBondWithoutDialog();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean cancelBondProcess();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelBondProcess();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBondOutOfBand(int, @Nullable android.bluetooth.OobData, @Nullable android.bluetooth.OobData);
method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getSimAccessPermission();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isEncrypted();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean removeBond();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int);
@@ -2000,51 +2002,51 @@
}
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean connect(android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean disconnect(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean connect(android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getHiSyncId(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothMap implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method public void close();
+ method protected void finalize();
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
}
public final class BluetoothMapClient implements android.bluetooth.BluetoothProfile {
- method @RequiresPermission(android.Manifest.permission.SEND_SMS) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.SEND_SMS}) public boolean sendMessage(@NonNull android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.net.Uri>, @NonNull String, @Nullable android.app.PendingIntent, @Nullable android.app.PendingIntent);
}
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
- method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isTetheringOn();
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setBluetoothTethering(boolean);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
- field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
- field public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn();
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.TETHER_PRIVILEGED}) public void setBluetoothTethering(boolean);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+ field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED";
field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
field public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE";
field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -2058,7 +2060,7 @@
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
- method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
}
@@ -2183,9 +2185,9 @@
package android.bluetooth.le {
public final class BluetoothLeScanner {
- method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADMIN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
- method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADMIN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
- method public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(android.os.WorkSource, android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.UPDATE_DEVICE_STATS}) public void startScanFromSource(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.os.WorkSource, android.bluetooth.le.ScanCallback);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(java.util.List<android.bluetooth.le.TruncatedFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
}
public final class ResultStorageDescriptor implements android.os.Parcelable {
@@ -7942,16 +7944,16 @@
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void registerControllerAlwaysOnListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
- method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnStateCallback(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback);
+ method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public void unregisterControllerAlwaysOnListener(@NonNull android.nfc.NfcAdapter.ControllerAlwaysOnListener);
field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
}
- public static interface NfcAdapter.ControllerAlwaysOnStateCallback {
- method public void onStateChanged(boolean);
+ public static interface NfcAdapter.ControllerAlwaysOnListener {
+ method public void onControllerAlwaysOnChanged(boolean);
}
public static interface NfcAdapter.NfcUnlockHandler {
@@ -8966,7 +8968,7 @@
package android.provider {
public class CallLog {
- method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPictureAsUser(@NonNull android.content.Context, @Nullable android.os.UserHandle, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
+ method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
}
public static class CallLog.CallComposerLoggingException extends java.lang.Throwable {
diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt
index b50b8dd..bf9f4f1 100644
--- a/core/api/system-removed.txt
+++ b/core/api/system-removed.txt
@@ -51,7 +51,7 @@
package android.bluetooth {
public final class BluetoothHeadset implements android.bluetooth.BluetoothProfile {
- method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
+ method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean setPriority(android.bluetooth.BluetoothDevice, int);
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 20e9187..0e1b24a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -161,6 +161,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode();
@@ -2009,6 +2010,7 @@
public final class PermissionManager {
method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData();
+ method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean);
method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource);
}
@@ -3172,5 +3174,12 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
}
+ @UiContext public abstract class WindowProviderService extends android.app.Service {
+ ctor public WindowProviderService();
+ method public final void attachToWindowToken(@NonNull android.os.IBinder);
+ method @Nullable public android.os.Bundle getWindowContextOptions();
+ method public abstract int getWindowType();
+ }
+
}
diff --git a/core/java/android/annotation/RequiresPermission.java b/core/java/android/annotation/RequiresPermission.java
index 1d89e31..303ab41 100644
--- a/core/java/android/annotation/RequiresPermission.java
+++ b/core/java/android/annotation/RequiresPermission.java
@@ -20,7 +20,7 @@
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -76,7 +76,7 @@
*
* @hide
*/
-@Retention(SOURCE)
+@Retention(CLASS)
@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
public @interface RequiresPermission {
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index db42803..a24555f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4272,7 +4272,8 @@
try {
getService().broadcastIntentWithFeature(
null, null, intent, null, null, Activity.RESULT_OK, null, null,
- null /*permission*/, appOp, null, false, true, userId);
+ null /*requiredPermissions*/, null /*excludedPermissions*/, appOp, null, false,
+ true, userId);
} catch (RemoteException ex) {
}
}
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 627017c..28d6fbb 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -469,7 +469,8 @@
}
}
- /** @hide */
+ /** Removes task by a given taskId */
+ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
public boolean removeTask(int taskId) {
try {
return getService().removeTask(taskId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8ff14b0..98fee9c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4390,11 +4390,12 @@
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
- ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
Application app = packageInfo.makeApplication(false, mInstrumentation);
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
+ final ContextImpl context = ContextImpl.getImpl(service
+ .createServiceBaseContext(this, packageInfo));
// Service resources must be initialized with the same loaders as the application
// context.
context.getResources().addLoaders(
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index dfc105a..8574678 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -614,7 +614,7 @@
* tombstone traces will be returned for
* {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with
* <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>.
- * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be
+ * Note that because these traces are kept in a separate global circular buffer, crashes may be
* overwritten by newer crashes (including from other applications), so this may still return
* null.
*
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index f7ea381..9753b67 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1176,8 +1176,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
- false, getUserId());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1194,7 +1194,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, null, false, false, getUserId());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1209,7 +1210,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, null, false, false, getUserId());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1224,7 +1226,24 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, false, false,
+ user.getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void sendBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions,
+ String[] excludedPermissions) {
+ warnIfCallingFromSystemProcess();
+ String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
+ try {
+ intent.prepareToLeaveProcess(this);
+ ActivityManager.getService().broadcastIntentWithFeature(
+ mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
+ null, Activity.RESULT_OK, null, null, receiverPermissions, excludedPermissions,
+ AppOpsManager.OP_NONE, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1241,7 +1260,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, options, false, false, getUserId());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
+ getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1257,8 +1277,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false,
- false, getUserId());
+ null, Activity.RESULT_OK, null, null, receiverPermissions,
+ null /*excludedPermissions=*/, appOp, null, false, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1275,7 +1295,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, null, true, false, getUserId());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, false,
+ getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1337,8 +1358,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- rd, initialCode, initialData, initialExtras, receiverPermissions, appOp,
- options, true, false, getUserId());
+ rd, initialCode, initialData, initialExtras, receiverPermissions,
+ null /*excludedPermissions=*/, appOp, options, true, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1351,8 +1372,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
- false, user.getIdentifier());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, null, false, false, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1375,7 +1396,8 @@
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
null, Activity.RESULT_OK, null, null, receiverPermissions,
- AppOpsManager.OP_NONE, options, false, false, user.getIdentifier());
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, options, false, false,
+ user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1391,8 +1413,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, receiverPermissions, appOp, null, false,
- false, user.getIdentifier());
+ null, Activity.RESULT_OK, null, null, receiverPermissions,
+ null /*excludedPermissions=*/, appOp, null, false, false, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1442,8 +1464,9 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- rd, initialCode, initialData, initialExtras, receiverPermissions, appOp,
- options, true, false, user.getIdentifier());
+ rd, initialCode, initialData, initialExtras, receiverPermissions,
+ null /*excludedPermissions=*/, appOp, options, true, false,
+ user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1483,8 +1506,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
- true, getUserId());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, null, false, true, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1522,8 +1545,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options,
- false, true, getUserId());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, options, false, true, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1558,8 +1581,9 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null,
- true, true, getUserId());
+ rd, initialCode, initialData, initialExtras, null,
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+ getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1590,8 +1614,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false,
- true, user.getIdentifier());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, null, false, true, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1605,8 +1629,8 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- null, Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, options,
- false, true, user.getIdentifier());
+ null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
+ AppOpsManager.OP_NONE, options, false, true, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1640,8 +1664,9 @@
intent.prepareToLeaveProcess(this);
ActivityManager.getService().broadcastIntentWithFeature(
mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
- rd, initialCode, initialData, initialExtras, null, AppOpsManager.OP_NONE, null,
- true, true, user.getIdentifier());
+ rd, initialCode, initialData, initialExtras, null,
+ null /*excludedPermissions=*/, AppOpsManager.OP_NONE, null, true, true,
+ user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f9279da..89d90a3 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -137,7 +137,7 @@
int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
int broadcastIntentWithFeature(in IApplicationThread caller, in String callingFeatureId,
in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode,
- in String resultData, in Bundle map, in String[] requiredPermissions,
+ in String resultData, in Bundle map, in String[] requiredPermissions, in String[] excludePermissions,
int appOp, in Bundle options, boolean serialized, boolean sticky, int userId);
void unbroadcastIntent(in IApplicationThread caller, in Intent intent, int userId);
@UnsupportedAppUsage
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d15d1b7..7ce0c70 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6315,7 +6315,7 @@
* Gets the theme's background color
*/
private @ColorInt int getDefaultBackgroundColor() {
- return obtainThemeColor(R.attr.colorBackground,
+ return obtainThemeColor(R.attr.colorSurface,
mInNightMode ? Color.BLACK : Color.WHITE);
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 4cf3a80..ca08683 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -53,7 +53,6 @@
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.ArraySet;
-import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.os.IResultReceiver;
@@ -371,19 +370,9 @@
"Cannot set both FLAG_IMMUTABLE and FLAG_MUTABLE for PendingIntent");
}
- // TODO(b/178092897) Remove the below instrumentation check and enforce
- // the explicit mutability requirement for apps under instrumentation.
- ActivityThread thread = ActivityThread.currentActivityThread();
- Instrumentation mInstrumentation = thread.getInstrumentation();
-
if (Compatibility.isChangeEnabled(PENDING_INTENT_EXPLICIT_MUTABILITY_REQUIRED)
&& !flagImmutableSet && !flagMutableSet) {
-
- if (mInstrumentation.isInstrumenting()) {
- Log.e(TAG, msg);
- } else {
throw new IllegalArgumentException(msg);
- }
}
}
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 2ceea7f..0ab3f2f 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -861,6 +861,19 @@
}
/**
+ * Creates the base {@link Context} of this {@link Service}.
+ * Users may override this API to create customized base context.
+ *
+ * @see android.window.WindowProviderService WindowProviderService class for example
+ * @see ContextWrapper#attachBaseContext(Context)
+ *
+ * @hide
+ */
+ public Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) {
+ return ContextImpl.createAppContext(mainThread, packageInfo);
+ }
+
+ /**
* @hide
* Clean up any references to avoid leaks.
*/
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index e93138b..759597c 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -28,6 +28,7 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
@@ -74,11 +75,8 @@
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
- // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN or CREDENTIAL_TYPE_PASSWORD.
- // Note that this class still uses CREDENTIAL_TYPE_PASSWORD to represent both numeric PIN
- // and alphabetic password. This is OK as long as this definition is only used internally,
- // and the value never gets mixed up with credential types from other parts of the framework.
- // TODO: fix this (ideally after we move logic to PasswordPolicy)
+ // One of CREDENTIAL_TYPE_NONE, CREDENTIAL_TYPE_PATTERN, CREDENTIAL_TYPE_PIN or
+ // CREDENTIAL_TYPE_PASSWORD.
public @CredentialType int credType;
// Fields below only make sense when credType is PASSWORD.
public int length = 0;
@@ -192,13 +190,15 @@
/**
* Returns the {@code PasswordMetrics} for a given credential.
*
- * If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
- * {@code credential} cannot be null when {@code type} is
+ * If the credential is a pin or a password, equivalent to
+ * {@link #computeForPasswordOrPin(byte[], boolean)}. {@code credential} cannot be null
+ * when {@code type} is
* {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
*/
public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
if (credential.isPassword() || credential.isPin()) {
- return PasswordMetrics.computeForPassword(credential.getCredential());
+ return PasswordMetrics.computeForPasswordOrPin(credential.getCredential(),
+ credential.isPin());
} else if (credential.isPattern()) {
return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
} else if (credential.isNone()) {
@@ -209,9 +209,9 @@
}
/**
- * Returns the {@code PasswordMetrics} for a given password
+ * Returns the {@code PasswordMetrics} for a given password or pin
*/
- public static PasswordMetrics computeForPassword(@NonNull byte[] password) {
+ public static PasswordMetrics computeForPasswordOrPin(byte[] password, boolean isPin) {
// Analyse the characters used
int letters = 0;
int upperCase = 0;
@@ -245,8 +245,9 @@
}
}
+ final int credType = isPin ? CREDENTIAL_TYPE_PIN : CREDENTIAL_TYPE_PASSWORD;
final int seqLength = maxLengthSequence(password);
- return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+ return new PasswordMetrics(credType, length, letters, upperCase, lowerCase,
numeric, symbols, nonLetter, nonNumeric, seqLength);
}
@@ -353,7 +354,7 @@
*/
public void maxWith(PasswordMetrics other) {
credType = Math.max(credType, other.credType);
- if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
return;
}
length = Math.max(length, other.length);
@@ -408,7 +409,7 @@
@Override
boolean allowsCredType(int credType) {
- return credType == CREDENTIAL_TYPE_PASSWORD;
+ return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
}
},
BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
@@ -424,7 +425,7 @@
@Override
boolean allowsCredType(int credType) {
- return credType == CREDENTIAL_TYPE_PASSWORD;
+ return credType == CREDENTIAL_TYPE_PASSWORD || credType == CREDENTIAL_TYPE_PIN;
}
},
BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
@@ -489,7 +490,7 @@
if (!bucket.allowsCredType(credType)) {
return false;
}
- if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ if (credType != CREDENTIAL_TYPE_PASSWORD && credType != CREDENTIAL_TYPE_PIN) {
return true;
}
return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
@@ -529,7 +530,7 @@
new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
}
- final PasswordMetrics enteredMetrics = computeForPassword(password);
+ final PasswordMetrics enteredMetrics = computeForPasswordOrPin(password, isPin);
return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
}
@@ -555,8 +556,8 @@
|| !bucket.allowsCredType(actualMetrics.credType)) {
return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
}
- // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
- if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+ if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD
+ && actualMetrics.credType != CREDENTIAL_TYPE_PIN) {
return Collections.emptyList(); // Nothing to check for pattern or none.
}
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
index 13f11ad..0544a36 100644
--- a/core/java/android/app/admin/PasswordPolicy.java
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -20,6 +20,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
@@ -27,6 +28,7 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
/**
* {@hide}
@@ -58,14 +60,20 @@
} else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
|| quality == PASSWORD_QUALITY_SOMETHING) {
return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
- } // quality is NUMERIC or stronger.
+ } else if (quality == PASSWORD_QUALITY_NUMERIC
+ || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PIN);
+ result.length = length;
+ if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+ }
+ return result;
+ } // quality is ALPHABETIC or stronger.
PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
result.length = length;
- if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
- result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
- } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+ if (quality == PASSWORD_QUALITY_ALPHABETIC) {
result.nonNumeric = 1;
} else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
result.numeric = 1;
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 16413e1..a268e16 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -23,6 +23,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -69,10 +72,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
@@ -90,10 +93,10 @@
*
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PLAYING_STATE_CHANGED =
"android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
@@ -112,11 +115,11 @@
* be null if no device is active. </li>
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage(trackingBug = 171933273)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -133,11 +136,11 @@
* connected, otherwise it is not included.</li>
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage(trackingBug = 181103983)
public static final String ACTION_CODEC_CONFIG_CHANGED =
@@ -307,7 +310,9 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
@@ -347,7 +352,9 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
@@ -368,6 +375,8 @@
* {@inheritDoc}
*/
@Override
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
try {
@@ -387,6 +396,8 @@
* {@inheritDoc}
*/
@Override
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
try {
@@ -406,6 +417,8 @@
* {@inheritDoc}
*/
@Override
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @BtProfileState int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
try {
@@ -441,7 +454,9 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage(trackingBug = 171933273)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
@@ -468,7 +483,9 @@
*/
@UnsupportedAppUsage(trackingBug = 171933273)
@Nullable
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getActiveDevice() {
if (VDBG) log("getActiveDevice()");
try {
@@ -495,7 +512,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -514,7 +534,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -546,7 +569,9 @@
* @return priority of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
@@ -620,6 +645,8 @@
* @param volume Absolute volume to be set on AVRCP side
* @hide
*/
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setAvrcpAbsoluteVolume(int volume) {
if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
try {
@@ -636,10 +663,11 @@
/**
* Check if A2DP profile is streaming music.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device BluetoothDevice device
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isA2dpPlaying(BluetoothDevice device) {
try {
final IBluetoothA2dp service = getService();
@@ -662,6 +690,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean shouldSendVolumeKeys(BluetoothDevice device) {
if (isEnabled() && isValidDevice(device)) {
ParcelUuid[] uuids = device.getUuids();
@@ -686,7 +715,9 @@
*/
@UnsupportedAppUsage(trackingBug = 181103983)
@Nullable
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
verifyDeviceNotNull(device, "getCodecStatus");
@@ -714,7 +745,9 @@
* @hide
*/
@UnsupportedAppUsage(trackingBug = 181103983)
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setCodecConfigPreference(@NonNull BluetoothDevice device,
@NonNull BluetoothCodecConfig codecConfig) {
if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
@@ -744,7 +777,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
verifyDeviceNotNull(device, "enableOptionalCodecs");
@@ -759,7 +794,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
verifyDeviceNotNull(device, "disableOptionalCodecs");
@@ -773,6 +810,7 @@
* active A2DP Bluetooth device.
* @param enable if true, enable the optional codecs, other disable them
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
try {
final IBluetoothA2dp service = getService();
@@ -800,7 +838,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@OptionalCodecsSupportStatus
public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
verifyDeviceNotNull(device, "isOptionalCodecsSupported");
@@ -826,7 +866,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@OptionalCodecsPreferenceStatus
public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
@@ -853,7 +895,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
@OptionalCodecsPreferenceStatus int value) {
verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java
index 67f3d7b..d81316e 100755
--- a/core/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/core/java/android/bluetooth/BluetoothA2dpSink.java
@@ -21,6 +21,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
@@ -160,7 +163,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -243,8 +248,6 @@
* Get the current audio configuration for the A2DP source device,
* or null if the device has no audio configuration
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Remote bluetooth device.
* @return audio configuration for the device, or null
*
@@ -252,6 +255,7 @@
*
* @hide
*/
+ @RequiresLegacyBluetoothPermission
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
if (VDBG) log("getAudioConfig(" + device + ")");
final IBluetoothA2dpSink service = getService();
@@ -278,7 +282,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -297,7 +304,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 79fd807..972e9e6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -17,7 +17,6 @@
package android.bluetooth;
-import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -25,11 +24,18 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.PeriodicAdvertisingManager;
@@ -98,11 +104,6 @@
* Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
* </p>
* <p>This class is thread safe.</p>
- * <p class="note"><strong>Note:</strong>
- * Most methods require the {@link android.Manifest.permission#BLUETOOTH}
- * permission and some also require the
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- * </p>
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>
@@ -144,8 +145,8 @@
* <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link
* #EXTRA_PREVIOUS_STATE} containing the new and old states
* respectively.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
@@ -278,8 +279,10 @@
* <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED}
* for global notification whenever the scan mode changes. For example, an
* application can be notified when the device has ended discoverability.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
@@ -305,8 +308,10 @@
* has rejected the request or an error has occurred.
* <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
* for global notification whenever Bluetooth is turned on or off.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
@@ -325,10 +330,12 @@
* has rejected the request or an error has occurred.
* <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
* for global notification whenever Bluetooth is turned on or off.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE";
@@ -355,8 +362,10 @@
* <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link
* #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes
* respectively.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
@@ -508,15 +517,19 @@
* progress, and existing connections will experience limited bandwidth
* and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
* discovery.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
/**
* Broadcast Action: The local Bluetooth adapter has finished the device
* discovery process.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
@@ -526,8 +539,10 @@
* <p>This name is visible to remote Bluetooth devices.
* <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing
* the name.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
/**
@@ -559,9 +574,10 @@
* {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE}
* can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
@@ -870,7 +886,7 @@
*
* @return true if the local adapter is turned on
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
public boolean isEnabled() {
return getState() == BluetoothAdapter.STATE_ON;
}
@@ -921,6 +937,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disableBLE() {
if (!isBleScanAlwaysAvailable()) {
return false;
@@ -966,6 +983,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean enableBLE() {
if (!isBleScanAlwaysAvailable()) {
return false;
@@ -986,6 +1004,7 @@
new PropertyInvalidatedCache<Void, Integer>(
8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
protected Integer recompute(Void query) {
try {
return mService.getState();
@@ -1039,7 +1058,7 @@
*
* @return current state of Bluetooth adapter
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
@AdapterState
public int getState() {
int state = getStateInternal();
@@ -1075,7 +1094,7 @@
* @return current state of Bluetooth adapter
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
@AdapterState
@UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine "
+ "whether you can use BLE & BT classic.")
@@ -1122,7 +1141,9 @@
*
* @return true to indicate adapter startup has begun, or false on immediate error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean enable() {
if (isEnabled()) {
if (DBG) {
@@ -1159,7 +1180,9 @@
*
* @return true to indicate adapter shutdown has begun, or false on immediate error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disable() {
try {
return mManagerService.disable(ActivityThread.currentPackageName(), true);
@@ -1172,13 +1195,13 @@
/**
* Turn off the local Bluetooth adapter and don't persist the setting.
*
- * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission
- *
* @return true to indicate adapter shutdown has begun, or false on immediate error
* @hide
*/
@UnsupportedAppUsage(trackingBug = 171933273)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disable(boolean persist) {
try {
@@ -1195,7 +1218,12 @@
*
* @return Bluetooth hardware address as string
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.LOCAL_MAC_ADDRESS,
+ })
public String getAddress() {
try {
return mManagerService.getAddress();
@@ -1208,10 +1236,12 @@
/**
* Get the friendly Bluetooth name of the local Bluetooth adapter.
* <p>This name is visible to remote Bluetooth devices.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @return the Bluetooth name, or null on error
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getName() {
try {
return mManagerService.getName();
@@ -1228,7 +1258,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean factoryReset() {
try {
mServiceLock.readLock().lock();
@@ -1253,7 +1283,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @Nullable ParcelUuid[] getUuids() {
if (getState() != STATE_ON) {
return null;
@@ -1285,7 +1317,9 @@
* @param name a valid Bluetooth name
* @return true if the name was set, false otherwise
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setName(String name) {
if (getState() != STATE_ON) {
return false;
@@ -1311,7 +1345,9 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothClass getBluetoothClass() {
if (getState() != STATE_ON) {
return null;
@@ -1340,7 +1376,7 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
if (getState() != STATE_ON) {
return false;
@@ -1367,7 +1403,9 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@IoCapability
public int getIoCapability() {
if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1395,7 +1433,7 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setIoCapability(@IoCapability int capability) {
if (getState() != STATE_ON) return false;
try {
@@ -1418,7 +1456,9 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@IoCapability
public int getLeIoCapability() {
if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
@@ -1446,7 +1486,7 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setLeIoCapability(@IoCapability int capability) {
if (getState() != STATE_ON) return false;
try {
@@ -1475,7 +1515,9 @@
*
* @return scan mode
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@ScanMode
public int getScanMode() {
if (getState() != STATE_ON) {
@@ -1522,7 +1564,9 @@
*/
@UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which "
+ "shows UI that confirms the user wants to go into discoverable mode.")
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean setScanMode(@ScanMode int mode, long durationMillis) {
if (getState() != STATE_ON) {
return false;
@@ -1571,7 +1615,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean setScanMode(@ScanMode int mode) {
if (getState() != STATE_ON) {
return false;
@@ -1591,6 +1637,7 @@
/** @hide */
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public int getDiscoverableTimeout() {
if (getState() != STATE_ON) {
return -1;
@@ -1610,6 +1657,7 @@
/** @hide */
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void setDiscoverableTimeout(int timeout) {
if (getState() != STATE_ON) {
return;
@@ -1635,7 +1683,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public long getDiscoveryEndMillis() {
try {
mServiceLock.readLock().lock();
@@ -1703,7 +1751,10 @@
*
* @return true on success, false on error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean startDiscovery() {
if (getState() != STATE_ON) {
return false;
@@ -1737,7 +1788,9 @@
*
* @return true on success, false on error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean cancelDiscovery() {
if (getState() != STATE_ON) {
return false;
@@ -1773,7 +1826,9 @@
*
* @return true if discovering
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean isDiscovering() {
if (getState() != STATE_ON) {
return false;
@@ -1805,7 +1860,11 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL
&& profiles != ACTIVE_DEVICE_ALL) {
@@ -1845,7 +1904,11 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean setActiveDevice(@NonNull BluetoothDevice device,
@ActiveDeviceUse int profiles) {
if (device == null) {
@@ -1889,7 +1952,11 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) {
try {
mServiceLock.readLock().lock();
@@ -1917,7 +1984,10 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) {
try {
mServiceLock.readLock().lock();
@@ -1938,6 +2008,7 @@
*
* @return true if Multiple Advertisement feature is supported
*/
+ @RequiresLegacyBluetoothPermission
public boolean isMultipleAdvertisementSupported() {
if (getState() != STATE_ON) {
return false;
@@ -1981,6 +2052,7 @@
new PropertyInvalidatedCache<Void, Boolean>(
8, BLUETOOTH_FILTERING_CACHE_PROPERTY) {
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
protected Boolean recompute(Void query) {
try {
mServiceLock.readLock().lock();
@@ -2012,6 +2084,7 @@
*
* @return true if chipset supports on-chip filtering
*/
+ @RequiresLegacyBluetoothPermission
public boolean isOffloadedFilteringSupported() {
if (!getLeAccess()) {
return false;
@@ -2024,6 +2097,7 @@
*
* @return true if chipset supports on-chip scan batching
*/
+ @RequiresLegacyBluetoothPermission
public boolean isOffloadedScanBatchingSupported() {
if (!getLeAccess()) {
return false;
@@ -2046,6 +2120,7 @@
*
* @return true if chipset supports LE 2M PHY feature
*/
+ @RequiresLegacyBluetoothPermission
public boolean isLe2MPhySupported() {
if (!getLeAccess()) {
return false;
@@ -2068,6 +2143,7 @@
*
* @return true if chipset supports LE Coded PHY feature
*/
+ @RequiresLegacyBluetoothPermission
public boolean isLeCodedPhySupported() {
if (!getLeAccess()) {
return false;
@@ -2090,6 +2166,7 @@
*
* @return true if chipset supports LE Extended Advertising feature
*/
+ @RequiresLegacyBluetoothPermission
public boolean isLeExtendedAdvertisingSupported() {
if (!getLeAccess()) {
return false;
@@ -2112,6 +2189,7 @@
*
* @return true if chipset supports LE Periodic Advertising feature
*/
+ @RequiresLegacyBluetoothPermission
public boolean isLePeriodicAdvertisingSupported() {
if (!getLeAccess()) {
return false;
@@ -2135,6 +2213,7 @@
*
* @return the maximum LE advertising data length.
*/
+ @RequiresLegacyBluetoothPermission
public int getLeMaximumAdvertisingDataLength() {
if (!getLeAccess()) {
return 0;
@@ -2172,7 +2251,9 @@
* @return the maximum number of connected audio devices
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getMaxConnectedAudioDevices() {
try {
mServiceLock.readLock().lock();
@@ -2193,6 +2274,7 @@
* @return true if there are hw entries available for matching beacons
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isHardwareTrackingFiltersAvailable() {
if (!getLeAccess()) {
return false;
@@ -2223,6 +2305,7 @@
* instead.
*/
@Deprecated
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
SynchronousResultReceiver receiver = new SynchronousResultReceiver();
requestControllerActivityEnergyInfo(receiver);
@@ -2248,6 +2331,7 @@
* @param result The callback to which to send the activity info.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void requestControllerActivityEnergyInfo(ResultReceiver result) {
try {
mServiceLock.readLock().lock();
@@ -2275,7 +2359,9 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() {
if (getState() != STATE_ON) {
return new ArrayList<>();
@@ -2303,7 +2389,9 @@
*
* @return unmodifiable set of {@link BluetoothDevice}, or null on error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public Set<BluetoothDevice> getBondedDevices() {
if (getState() != STATE_ON) {
return toDeviceSet(new BluetoothDevice[0]);
@@ -2368,6 +2456,7 @@
* This method must not be called when mService is null.
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
protected Integer recompute(Void query) {
try {
return mService.getAdapterConnectionState();
@@ -2401,6 +2490,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresLegacyBluetoothPermission
public int getConnectionState() {
if (getState() != STATE_ON) {
return BluetoothAdapter.STATE_DISCONNECTED;
@@ -2429,6 +2519,7 @@
new PropertyInvalidatedCache<Integer, Integer>(
8, BLUETOOTH_PROFILE_CACHE_PROPERTY) {
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
protected Integer recompute(Integer query) {
try {
mServiceLock.readLock().lock();
@@ -2471,7 +2562,10 @@
* {@link BluetoothProfile#STATE_CONNECTED},
* {@link BluetoothProfile#STATE_DISCONNECTING}
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public int getProfileConnectionState(int profile) {
if (getState() != STATE_ON) {
return BluetoothProfile.STATE_DISCONNECTED;
@@ -2486,7 +2580,6 @@
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
* connections from a listening {@link BluetoothServerSocket}.
* <p>Valid RFCOMM channels are in range 1 to 30.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param channel RFCOMM channel to listen on
* @return a listening RFCOMM BluetoothServerSocket
@@ -2494,6 +2587,9 @@
* permissions, or channel in use.
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
return listenUsingRfcommOn(channel, false, false);
}
@@ -2505,7 +2601,6 @@
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
* connections from a listening {@link BluetoothServerSocket}.
* <p>Valid RFCOMM channels are in range 1 to 30.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
* <p>To auto assign a channel without creating a SDP record use
* {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number.
*
@@ -2519,6 +2614,9 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm,
boolean min16DigitPin) throws IOException {
BluetoothServerSocket socket =
@@ -2559,7 +2657,9 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or channel in use.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
throws IOException {
return createNewRfcommSocketAndRecord(name, uuid, true, true);
@@ -2591,7 +2691,9 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or channel in use.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
throws IOException {
return createNewRfcommSocketAndRecord(name, uuid, false, false);
@@ -2622,7 +2724,6 @@
* closed, or if this application closes unexpectedly.
* <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
* connect to this socket from another device using the same {@link UUID}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param name service name for SDP record
* @param uuid uuid for SDP record
@@ -2632,12 +2733,15 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid)
throws IOException {
return createNewRfcommSocketAndRecord(name, uuid, false, true);
}
-
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid,
boolean auth, boolean encrypt) throws IOException {
BluetoothServerSocket socket;
@@ -2663,6 +2767,7 @@
* permissions.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
BluetoothServerSocket socket =
new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port);
@@ -2694,6 +2799,7 @@
* permissions.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin)
throws IOException {
BluetoothServerSocket socket =
@@ -2726,11 +2832,11 @@
* permissions.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
return listenUsingL2capOn(port, false, false);
}
-
/**
* Construct an insecure L2CAP server socket.
* Call #accept to retrieve connections to this socket.
@@ -2743,6 +2849,7 @@
* permissions.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException {
Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port);
BluetoothServerSocket socket =
@@ -2769,11 +2876,14 @@
/**
* Read the local Out of Band Pairing Data
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @return Pair<byte[], byte[]> of Hash and Randomizer
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public Pair<byte[], byte[]> readOutOfBandData() {
return null;
}
@@ -2863,6 +2973,7 @@
* @param profile
* @param proxy Profile proxy object
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void closeProfileProxy(int profile, BluetoothProfile proxy) {
if (proxy == null) {
return;
@@ -2937,6 +3048,7 @@
private final IBluetoothManagerCallback mManagerCallback =
new IBluetoothManagerCallback.Stub() {
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onBluetoothServiceUp(IBluetooth bluetoothService) {
if (DBG) {
Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
@@ -3031,7 +3143,9 @@
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean enableNoAutoConnect() {
if (isEnabled()) {
if (DBG) {
@@ -3095,8 +3209,6 @@
*
* @param transport - whether the {@link OobData} is generated for LE or Classic.
* @param oobData - data generated in the host stack(LE) or controller (Classic)
- *
- * @hide
*/
void onOobData(@Transport int transport, @Nullable OobData oobData);
@@ -3104,8 +3216,6 @@
* Provides feedback when things don't go as expected.
*
* @param errorCode - the code descibing the type of error that occurred.
- *
- * @hide
*/
void onError(@OobError int errorCode);
}
@@ -3188,7 +3298,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void generateLocalOobData(@Transport int transport,
@NonNull @CallbackExecutor Executor executor, @NonNull OobDataCallback callback) {
if (transport != BluetoothDevice.TRANSPORT_BREDR && transport
@@ -3232,12 +3342,14 @@
* reason. If Bluetooth is already on and if this function is called to turn
* it on, the api will return true and a callback will be called.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
- *
* @param on True for on, false for off.
* @param callback The callback to notify changes to the state.
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public boolean changeApplicationBluetoothState(boolean on,
BluetoothStateChangeCallback callback) {
return false;
@@ -3256,6 +3368,7 @@
/**
* @hide
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub {
private BluetoothStateChangeCallback mCallback;
@@ -3447,7 +3560,10 @@
* instead.
*/
@Deprecated
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean startLeScan(LeScanCallback callback) {
return startLeScan(null, callback);
}
@@ -3466,7 +3582,10 @@
* instead.
*/
@Deprecated
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
if (DBG) {
Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids));
@@ -3563,7 +3682,9 @@
* @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead.
*/
@Deprecated
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void stopLeScan(LeScanCallback callback) {
if (DBG) {
Log.d(TAG, "stopLeScan()");
@@ -3604,7 +3725,9 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or unable to start this CoC
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull BluetoothServerSocket listenUsingL2capChannel()
throws IOException {
BluetoothServerSocket socket =
@@ -3650,7 +3773,9 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions, or unable to start this CoC
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel()
throws IOException {
BluetoothServerSocket socket =
@@ -3695,7 +3820,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device,
@NonNull Executor executor, @NonNull OnMetadataChangedListener listener) {
if (DBG) Log.d(TAG, "addOnMetadataChangedListener()");
@@ -3768,7 +3893,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device,
@NonNull OnMetadataChangedListener listener) {
if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()");
@@ -3857,6 +3982,7 @@
* @throws IllegalArgumentException if the callback is already registered
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothConnectionCallback callback) {
if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()");
@@ -3899,6 +4025,7 @@
* @return true if the callback was unregistered successfully, false otherwise
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean unregisterBluetoothConnectionCallback(
@NonNull BluetoothConnectionCallback callback) {
if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()");
diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java
index 4e7e441..887cf3f 100644
--- a/core/java/android/bluetooth/BluetoothAvrcpController.java
+++ b/core/java/android/bluetooth/BluetoothAvrcpController.java
@@ -16,6 +16,10 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -54,10 +58,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 0c208fd..1201663 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -16,7 +16,6 @@
package android.bluetooth;
-import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,6 +25,11 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PropertyInvalidatedCache;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.companion.AssociationRequest;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -66,9 +70,6 @@
* {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using
* {@link #createL2capChannel(int)} over Bluetooth LE.
*
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>
@@ -108,10 +109,12 @@
* <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
* #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
* {@link #EXTRA_RSSI} if they are available.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} and
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to receive.
*/
// TODO: Change API to not broadcast RSSI if not available (incoming connection)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_FOUND =
"android.bluetooth.device.action.FOUND";
@@ -120,9 +123,11 @@
* Broadcast Action: Bluetooth class of a remote device has changed.
* <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
* #EXTRA_CLASS}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
* {@see BluetoothClass}
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CLASS_CHANGED =
"android.bluetooth.device.action.CLASS_CHANGED";
@@ -133,8 +138,10 @@
* <p>Always contains the extra field {@link #EXTRA_DEVICE}.
* <p>ACL connections are managed automatically by the Android Bluetooth
* stack.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ACL_CONNECTED =
"android.bluetooth.device.action.ACL_CONNECTED";
@@ -146,8 +153,10 @@
* this intent as a hint to immediately terminate higher level connections
* (RFCOMM, L2CAP, or profile connections) to the remote device.
* <p>Always contains the extra field {@link #EXTRA_DEVICE}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ACL_DISCONNECT_REQUESTED =
"android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
@@ -158,8 +167,10 @@
* <p>Always contains the extra field {@link #EXTRA_DEVICE}.
* <p>ACL connections are managed automatically by the Android Bluetooth
* stack.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ACL_DISCONNECTED =
"android.bluetooth.device.action.ACL_DISCONNECTED";
@@ -169,8 +180,10 @@
* been retrieved for the first time, or changed since the last retrieval.
* <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
* #EXTRA_NAME}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NAME_CHANGED =
"android.bluetooth.device.action.NAME_CHANGED";
@@ -179,9 +192,11 @@
* Broadcast Action: Indicates the alias of a remote device has been
* changed.
* <p>Always contains the extra field {@link #EXTRA_DEVICE}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
@SuppressLint("ActionValue")
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ALIAS_CHANGED =
"android.bluetooth.device.action.ALIAS_CHANGED";
@@ -191,10 +206,12 @@
* device. For example, if a device is bonded (paired).
* <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link
* #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*/
// Note: When EXTRA_BOND_STATE is BOND_NONE then this will also
// contain a hidden extra field EXTRA_REASON with the result code.
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BOND_STATE_CHANGED =
"android.bluetooth.device.action.BOND_STATE_CHANGED";
@@ -204,10 +221,12 @@
* been retrieved for the first time, or changed since the last retrieval
* <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
* #EXTRA_BATTERY_LEVEL}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_BATTERY_LEVEL_CHANGED =
"android.bluetooth.device.action.BATTERY_LEVEL_CHANGED";
@@ -642,8 +661,10 @@
* device are requested to be fetched using Service Discovery Protocol
* <p> Always contains the extra field {@link #EXTRA_DEVICE}
* <p> Always contains the extra field {@link #EXTRA_UUID}
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to receive.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_UUID =
"android.bluetooth.device.action.UUID";
@@ -657,20 +678,23 @@
* Broadcast Action: Indicates a failure to retrieve the name of a remote
* device.
* <p>Always contains the extra field {@link #EXTRA_DEVICE}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
*
* @hide
*/
//TODO: is this actually useful?
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NAME_FAILED =
"android.bluetooth.device.action.NAME_FAILED";
/**
* Broadcast Action: This intent is used to broadcast PAIRING REQUEST
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to
- * receive.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PAIRING_REQUEST =
"android.bluetooth.device.action.PAIRING_REQUEST";
@@ -1206,7 +1230,9 @@
*
* @return the Bluetooth name, or null if there was a problem.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getName() {
final IBluetooth service = sService;
if (service == null) {
@@ -1235,7 +1261,9 @@
* @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link
* #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getType() {
final IBluetooth service = sService;
if (service == null) {
@@ -1257,7 +1285,9 @@
* null if there was a problem
*/
@Nullable
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getAlias() {
final IBluetooth service = sService;
if (service == null) {
@@ -1293,7 +1323,9 @@
* @return {@code true} if the alias is successfully set, {@code false} on error
* @throws IllegalArgumentException if the alias is {@code null} or the empty string
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setAlias(@NonNull String alias) {
if (alias == null || alias.isEmpty()) {
throw new IllegalArgumentException("Cannot set the alias to null or the empty string");
@@ -1321,7 +1353,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getBatteryLevel() {
final IBluetooth service = sService;
if (service == null) {
@@ -1346,7 +1380,9 @@
*
* @return false on immediate error, true if bonding will begin
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean createBond() {
return createBond(TRANSPORT_AUTO);
}
@@ -1367,7 +1403,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean createBond(int transport) {
return createBondInternal(transport, null, null);
}
@@ -1395,7 +1433,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
@Nullable OobData remoteP256Data) {
if (remoteP192Data == null && remoteP256Data == null) {
@@ -1406,6 +1444,7 @@
return createBondInternal(transport, remoteP192Data, remoteP256Data);
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
@Nullable OobData remoteP256Data) {
final IBluetooth service = sService;
@@ -1430,7 +1469,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isBondingInitiatedLocally() {
final IBluetooth service = sService;
if (service == null) {
@@ -1452,7 +1493,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean cancelBondProcess() {
final IBluetooth service = sService;
if (service == null) {
@@ -1480,7 +1521,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean removeBond() {
final IBluetooth service = sService;
if (service == null) {
@@ -1504,6 +1545,7 @@
new PropertyInvalidatedCache<BluetoothDevice, Integer>(
8, BLUETOOTH_BONDING_CACHE_PROPERTY) {
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
protected Integer recompute(BluetoothDevice query) {
try {
return sService.getBondState(query);
@@ -1532,7 +1574,10 @@
*
* @return the bond state
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public int getBondState() {
final IBluetooth service = sService;
if (service == null) {
@@ -1560,7 +1605,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean canBondWithoutDialog() {
final IBluetooth service = sService;
if (service == null) {
@@ -1583,7 +1628,9 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isConnected() {
final IBluetooth service = sService;
if (service == null) {
@@ -1606,7 +1653,9 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isEncrypted() {
final IBluetooth service = sService;
if (service == null) {
@@ -1626,7 +1675,9 @@
*
* @return Bluetooth class object, or null on error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothClass getBluetoothClass() {
final IBluetooth service = sService;
if (service == null) {
@@ -1653,7 +1704,9 @@
*
* @return the supported features (UUIDs) of the remote device, or null on error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public ParcelUuid[] getUuids() {
final IBluetooth service = sService;
if (service == null || !isBluetoothEnabled()) {
@@ -1681,7 +1734,9 @@
* @return False if the check fails, True if the process of initiating an ACL connection
* to the remote device was started.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean fetchUuidsWithSdp() {
final IBluetooth service = sService;
if (service == null || !isBluetoothEnabled()) {
@@ -1707,8 +1762,7 @@
* {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
* Detailed status error codes can be found by members of the Bluetooth package in
* the AbstractionLayer class.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
- * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
+ * <p>The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
* The object type will match one of the SdpXxxRecord types, depending on the UUID searched
* for.
*
@@ -1717,6 +1771,9 @@
* was started.
*/
/** @hide */
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sdpSearch(ParcelUuid uuid) {
final IBluetooth service = sService;
if (service == null) {
@@ -1733,10 +1790,12 @@
/**
* Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @return true pin has been set false for error
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setPin(byte[] pin) {
final IBluetooth service = sService;
if (service == null) {
@@ -1758,7 +1817,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setPin(@NonNull String pin) {
byte[] pinBytes = convertPinToBytes(pin);
if (pinBytes == null) {
@@ -1772,7 +1833,7 @@
*
* @return true confirmation has been sent out false for error
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setPairingConfirmation(boolean confirm) {
final IBluetooth service = sService;
if (service == null) {
@@ -1795,7 +1856,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean cancelPairing() {
final IBluetooth service = sService;
if (service == null) {
@@ -1827,7 +1890,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @AccessPermission int getPhonebookAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
@@ -1859,8 +1924,6 @@
* If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
* enter silence mode.
*
- * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
- *
* @param silence true to enter silence mode, false to exit
* @return true on success, false on error.
* @throws IllegalStateException if Bluetooth is not turned ON.
@@ -1884,8 +1947,6 @@
/**
* Check whether the {@link BluetoothDevice} is in silence mode
*
- * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
- *
* @return true on device in silence mode, otherwise false.
* @throws IllegalStateException if Bluetooth is not turned ON.
* @hide
@@ -1935,7 +1996,9 @@
* @hide
*/
@UnsupportedAppUsage
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @AccessPermission int getMessageAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
@@ -1959,7 +2022,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setMessageAccessPermission(@AccessPermission int value) {
// Validates param value is one of the accepted constants
if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) {
@@ -1984,7 +2047,9 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @AccessPermission int getSimAccessPermission() {
final IBluetooth service = sService;
if (service == null) {
@@ -2008,7 +2073,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setSimAccessPermission(int value) {
final IBluetooth service = sService;
if (service == null) {
@@ -2039,7 +2104,6 @@
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection.
* <p>Valid RFCOMM channels are in range 1 to 30.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param channel RFCOMM channel to connect to
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2048,6 +2112,10 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createRfcommSocket(int channel) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
@@ -2074,7 +2142,6 @@
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection.
* <p>Valid L2CAP PSM channels are in range 1 to 2^16.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param channel L2cap PSM/channel to connect to
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2082,6 +2149,10 @@
* permissions
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createL2capSocket(int channel) throws IOException {
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
null);
@@ -2095,7 +2166,6 @@
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
* connection.
* <p>Valid L2CAP PSM channels are in range 1 to 2^16.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
*
* @param channel L2cap PSM/channel to connect to
* @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
@@ -2103,6 +2173,10 @@
* permissions
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException {
return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel,
null);
@@ -2138,7 +2212,10 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
@@ -2176,7 +2253,10 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
@@ -2192,7 +2272,6 @@
* Call #connect on the returned #BluetoothSocket to begin the connection.
* The remote device will not be authenticated and communication on this
* socket will not be encrypted.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param port remote port
* @return An RFCOMM BluetoothSocket
@@ -2202,6 +2281,10 @@
*/
@UnsupportedAppUsage(publicAlternatives = "Use "
+ "{@link #createInsecureRfcommSocketToServiceRecord} instead.")
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
@@ -2214,7 +2297,6 @@
/**
* Construct a SCO socket ready to start an outgoing connection.
* Call #connect on the returned #BluetoothSocket to begin the connection.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @return a SCO BluetoothSocket
* @throws IOException on error, for example Bluetooth not available, or insufficient
@@ -2222,6 +2304,10 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public BluetoothSocket createScoSocket() throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "Bluetooth is not enabled");
@@ -2269,6 +2355,8 @@
* automatically connect as soon as the remote device becomes available (true).
* @throws IllegalArgumentException if callback is null
*/
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback) {
return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
@@ -2289,6 +2377,8 @@
* BluetoothDevice#TRANSPORT_LE}
* @throws IllegalArgumentException if callback is null
*/
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport) {
return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK));
@@ -2313,6 +2403,8 @@
* is set to true.
* @throws NullPointerException if callback is null
*/
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport, int phy) {
return connectGatt(context, autoConnect, callback, transport, phy, null);
@@ -2339,6 +2431,8 @@
* an un-specified background thread.
* @throws NullPointerException if callback is null
*/
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport, int phy,
Handler handler) {
@@ -2372,6 +2466,8 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport,
boolean opportunistic, int phy, Handler handler) {
@@ -2416,7 +2512,10 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "createL2capChannel: Bluetooth is not enabled");
@@ -2444,7 +2543,10 @@
* @throws IOException on error, for example Bluetooth not available, or insufficient
* permissions
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException {
if (!isBluetoothEnabled()) {
Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled");
@@ -2472,7 +2574,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) {
final IBluetooth service = sService;
if (service == null) {
@@ -2500,7 +2602,7 @@
*/
@SystemApi
@Nullable
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public byte[] getMetadata(@MetadataKey int key) {
final IBluetooth service = sService;
if (service == null) {
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index 381318b..942f843 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -17,6 +17,14 @@
package android.bluetooth;
import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
import android.os.Build;
import android.os.Handler;
import android.os.ParcelUuid;
@@ -157,6 +165,7 @@
* @hide
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onClientRegistered(int status, int clientIf) {
if (DBG) {
Log.d(TAG, "onClientRegistered() - status=" + status
@@ -347,6 +356,7 @@
* @hide
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onCharacteristicRead(String address, int status, int handle,
byte[] value) {
if (VDBG) {
@@ -404,6 +414,7 @@
* @hide
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onCharacteristicWrite(String address, int status, int handle) {
if (VDBG) {
Log.d(TAG, "onCharacteristicWrite() - Device=" + address
@@ -487,6 +498,7 @@
* @hide
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onDescriptorRead(String address, int status, int handle, byte[] value) {
if (VDBG) {
Log.d(TAG,
@@ -538,6 +550,7 @@
* @hide
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onDescriptorWrite(String address, int status, int handle) {
if (VDBG) {
Log.d(TAG,
@@ -734,6 +747,7 @@
* Application should call this method as early as possible after it is done with
* this GATT client.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void close() {
if (DBG) Log.d(TAG, "close()");
@@ -817,12 +831,13 @@
* <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
* is used to notify success or failure if the function returns true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @return If true, the callback will be called to notify success or failure, false on immediate
* error
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
return registerApp(callback, handler, false);
}
@@ -833,14 +848,15 @@
* <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
* is used to notify success or failure if the function returns true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @param eatt_support indicate to allow for eatt support
* @return If true, the callback will be called to notify success or failure, false on immediate
* error
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean registerApp(BluetoothGattCallback callback, Handler handler,
boolean eatt_support) {
if (DBG) Log.d(TAG, "registerApp()");
@@ -865,6 +881,7 @@
* Unregister the current application and callbacks.
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void unregisterApp() {
if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
if (mService == null || mClientIf == 0) return;
@@ -893,14 +910,15 @@
* subsequent connections to known devices should be invoked with the
* autoConnect parameter set to true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Remote device to connect to
* @param autoConnect Whether to directly connect to the remote device (false) or to
* automatically connect as soon as the remote device becomes available (true).
* @return true, if the connection attempt was initiated successfully
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
/*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
Handler handler) {
if (DBG) {
@@ -931,9 +949,10 @@
/**
* Disconnects an established connection, or cancels a connection attempt
* currently in progress.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void disconnect() {
if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return;
@@ -954,6 +973,7 @@
*
* @return true, if the connection attempt was initiated successfully
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect() {
try {
mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport,
@@ -983,6 +1003,7 @@
* of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
* {@link BluetoothDevice#PHY_OPTION_S8}
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
try {
mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
@@ -996,6 +1017,7 @@
* Read the current transmitter PHY and receiver PHY of the connection. The values are returned
* in {@link BluetoothGattCallback#onPhyRead}
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void readPhy() {
try {
mService.clientReadPhy(mClientIf, mDevice.getAddress());
@@ -1022,10 +1044,11 @@
* triggered. If the discovery was successful, the remote services can be
* retrieved using the {@link #getServices} function.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the remote service discovery has been started
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean discoverServices() {
if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1047,11 +1070,12 @@
* It should never be used by real applications. The service is not searched
* for characteristics and descriptors, or returned in any callback.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the remote service discovery has been started
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean discoverServiceByUuid(UUID uuid) {
if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1073,11 +1097,10 @@
* <p>This function requires that service discovery has been completed
* for the given device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return List of services on the remote device. Returns an empty list if service discovery has
* not yet been performed.
*/
+ @RequiresLegacyBluetoothPermission
public List<BluetoothGattService> getServices() {
List<BluetoothGattService> result =
new ArrayList<BluetoothGattService>();
@@ -1101,12 +1124,11 @@
* <p>If multiple instances of the same service (as identified by UUID)
* exist, the first instance of the service is returned.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param uuid UUID of the requested service
* @return BluetoothGattService if supported, or null if the requested service is not offered by
* the remote device.
*/
+ @RequiresLegacyBluetoothPermission
public BluetoothGattService getService(UUID uuid) {
for (BluetoothGattService service : mServices) {
if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) {
@@ -1124,11 +1146,12 @@
* is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
* callback.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param characteristic Characteristic to read from the remote device
* @return true, if the read operation was initiated successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
return false;
@@ -1167,12 +1190,13 @@
* is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
* callback.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param uuid UUID of characteristic to read from the remote device
* @return true, if the read operation was initiated successfully
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
if (mService == null || mClientIf == 0) return false;
@@ -1202,11 +1226,12 @@
* {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
* reporting the result of the operation.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param characteristic Characteristic to write on the remote device
* @return true, if the write operation was initiated successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
&& (characteristic.getProperties()
@@ -1248,11 +1273,12 @@
* {@link BluetoothGattCallback#onDescriptorRead} callback is
* triggered, signaling the result of the operation.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param descriptor Descriptor value to read from the remote device
* @return true, if the read operation was initiated successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
if (mService == null || mClientIf == 0) return false;
@@ -1289,11 +1315,12 @@
* <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
* triggered to report the result of the write operation.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param descriptor Descriptor to write to the associated remote device
* @return true, if the write operation was initiated successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false;
@@ -1340,10 +1367,11 @@
* cancel the current transaction without committing any values on the
* remote device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the reliable write transaction has been initiated
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean beginReliableWrite() {
if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1367,10 +1395,11 @@
* <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
* invoked to indicate whether the transaction has been executed correctly.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the request to execute the transaction has been sent
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean executeReliableWrite() {
if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1396,9 +1425,10 @@
*
* <p>Calling this function will discard all queued characteristic write
* operations for a given remote device.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void abortReliableWrite() {
if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return;
@@ -1414,6 +1444,7 @@
* @deprecated Use {@link #abortReliableWrite()}
*/
@Deprecated
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void abortReliableWrite(BluetoothDevice mDevice) {
abortReliableWrite();
}
@@ -1426,12 +1457,13 @@
* triggered if the remote device indicates that the given characteristic
* has changed.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param characteristic The characteristic for which to enable notifications
* @param enable Set to true to enable notifications/indications
* @return true, if the requested notification status was set successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
boolean enable) {
if (DBG) {
@@ -1464,6 +1496,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean refresh() {
if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1484,10 +1517,11 @@
* <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
* invoked when the RSSI value has been read.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the RSSI value has been requested successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readRemoteRssi() {
if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return false;
@@ -1512,10 +1546,11 @@
* <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
* whether this operation was successful.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true, if the new MTU value has been requested successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestMtu(int mtu) {
if (DBG) {
Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
@@ -1544,6 +1579,7 @@
* or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
* @throws IllegalArgumentException If the parameters are outside of their specified range.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestConnectionPriority(int connectionPriority) {
if (connectionPriority < CONNECTION_PRIORITY_BALANCED
|| connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
@@ -1571,6 +1607,7 @@
* @return true, if the request is send to the Bluetooth stack.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval,
int slaveLatency, int supervisionTimeout,
int minConnectionEventLen, int maxConnectionEventLen) {
diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
index 8f1b59c..8a7d4ba 100644
--- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -237,7 +237,6 @@
/**
* Create a new BluetoothGattCharacteristic.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param uuid The UUID for this characteristic
* @param properties Properties of this characteristic
@@ -344,7 +343,6 @@
/**
* Adds a descriptor to this characteristic.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param descriptor Descriptor to be added to this characteristic.
* @return true, if the descriptor was added to the characteristic
diff --git a/core/java/android/bluetooth/BluetoothGattDescriptor.java b/core/java/android/bluetooth/BluetoothGattDescriptor.java
index 49ba281..ed5ea08 100644
--- a/core/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -128,7 +128,6 @@
/**
* Create a new BluetoothGattDescriptor.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param uuid The UUID for this descriptor
* @param permissions Permissions for this descriptor
@@ -139,7 +138,6 @@
/**
* Create a new BluetoothGattDescriptor.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param characteristic The characteristic this descriptor belongs to
* @param uuid The UUID for this descriptor
@@ -228,8 +226,6 @@
* <p>If a remote device offers multiple descriptors with the same UUID,
* the instance ID is used to distuinguish between descriptors.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return Instance ID of this descriptor
* @hide
*/
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 088b016..fdb8018 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -16,6 +16,9 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Log;
@@ -425,6 +428,7 @@
* Application should call this method as early as possible after it is done with
* this GATT server.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void close() {
if (DBG) Log.d(TAG, "close()");
unregisterCallback();
@@ -436,12 +440,13 @@
* <p>This is an asynchronous call. The callback is used to notify
* success or failure if the function returns true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @return true, the callback will be called to notify success or failure, false on immediate
* error
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
return registerCallback(callback, false);
}
@@ -452,14 +457,15 @@
* <p>This is an asynchronous call. The callback is used to notify
* success or failure if the function returns true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @param eatt_support indicates if server can use eatt
* @return true, the callback will be called to notify success or failure, false on immediate
* error
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback,
boolean eatt_support) {
if (DBG) Log.d(TAG, "registerCallback()");
@@ -504,6 +510,7 @@
/**
* Unregister the current application and callbacks.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void unregisterCallback() {
if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
if (mService == null || mServerIf == 0) return;
@@ -548,12 +555,13 @@
* subsequent connections to known devices should be invoked with the
* autoConnect parameter set to true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param autoConnect Whether to directly connect to the remote device (false) or to
* automatically connect as soon as the remote device becomes available (true).
* @return true, if the connection attempt was initiated successfully
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(BluetoothDevice device, boolean autoConnect) {
if (DBG) {
Log.d(TAG,
@@ -576,10 +584,11 @@
* Disconnects an established connection, or cancels a connection attempt
* currently in progress.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Remote device
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void cancelConnection(BluetoothDevice device) {
if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress());
if (mService == null || mServerIf == 0) return;
@@ -609,6 +618,7 @@
* of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
* {@link BluetoothDevice#PHY_OPTION_S8}
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
try {
mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
@@ -624,6 +634,7 @@
*
* @param device The remote device to send this response to
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void readPhy(BluetoothDevice device) {
try {
mService.serverReadPhy(mServerIf, device.getAddress());
@@ -645,14 +656,15 @@
* <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device The remote device to send this response to
* @param requestId The ID of the request that was received with the callback
* @param status The status of the request to be sent to the remote devices
* @param offset Value offset for partial read/write response
* @param value The value of the attribute that was read/written (optional)
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendResponse(BluetoothDevice device, int requestId,
int status, int offset, byte[] value) {
if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
@@ -677,8 +689,6 @@
* for every client that requests notifications/indications by writing
* to the "Client Configuration" descriptor for the given characteristic.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device The remote device to receive the notification/indication
* @param characteristic The local characteristic that has been updated
* @param confirm true to request confirmation from the client (indication), false to send a
@@ -686,6 +696,9 @@
* @return true, if the notification has been triggered successfully
* @throws IllegalArgumentException
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean notifyCharacteristicChanged(BluetoothDevice device,
BluetoothGattCharacteristic characteristic, boolean confirm) {
if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
@@ -724,11 +737,12 @@
* whether this service has been added successfully. Do not add another service
* before this callback.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param service Service to be added to the list of services provided by this device.
* @return true, if the request to add service has been initiated
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean addService(BluetoothGattService service) {
if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
if (mService == null || mServerIf == 0) return false;
@@ -748,11 +762,12 @@
/**
* Removes a service from the list of services to be provided.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param service Service to be removed.
* @return true, if the service has been removed
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean removeService(BluetoothGattService service) {
if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
if (mService == null || mServerIf == 0) return false;
@@ -774,8 +789,10 @@
/**
* Remove all services from the list of provided services.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void clearServices() {
if (DBG) Log.d(TAG, "clearServices()");
if (mService == null || mServerIf == 0) return;
@@ -794,10 +811,9 @@
* <p>An application must call {@link #addService} to add a serice to the
* list of services offered by this device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return List of services. Returns an empty list if no services have been added yet.
*/
+ @RequiresLegacyBluetoothPermission
public List<BluetoothGattService> getServices() {
return mServices;
}
@@ -809,12 +825,11 @@
* <p>If multiple instances of the same service (as identified by UUID)
* exist, the first instance of the service is returned.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param uuid UUID of the requested service
* @return BluetoothGattService if supported, or null if the requested service is not offered by
* this device.
*/
+ @RequiresLegacyBluetoothPermission
public BluetoothGattService getService(UUID uuid) {
for (BluetoothGattService service : mServices) {
if (service.getUuid().equals(uuid)) {
diff --git a/core/java/android/bluetooth/BluetoothGattService.java b/core/java/android/bluetooth/BluetoothGattService.java
index 23dc7c8..f64d09f 100644
--- a/core/java/android/bluetooth/BluetoothGattService.java
+++ b/core/java/android/bluetooth/BluetoothGattService.java
@@ -15,6 +15,9 @@
*/
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
@@ -98,7 +101,6 @@
/**
* Create a new BluetoothGattService.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param uuid The UUID for this service
* @param serviceType The type of this service,
@@ -225,11 +227,11 @@
/**
* Add an included service to this service.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param service The service to be added
* @return true, if the included service was added to the service
*/
+ @RequiresLegacyBluetoothPermission
public boolean addService(BluetoothGattService service) {
mIncludedServices.add(service);
return true;
@@ -237,11 +239,11 @@
/**
* Add a characteristic to this service.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param characteristic The characteristics to be added
* @return true, if the characteristic was added to the service
*/
+ @RequiresLegacyBluetoothPermission
public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) {
mCharacteristics.add(characteristic);
characteristic.setService(this);
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index 632572d..84e8c51 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -22,6 +22,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -70,10 +73,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
@@ -90,10 +93,10 @@
* </ul>
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
- * to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_AUDIO_STATE_CHANGED =
"android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
@@ -107,11 +110,11 @@
* be null if no device is active. </li>
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage(trackingBug = 171933273)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -147,9 +150,10 @@
* <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
* <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
* </ul>
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
- * to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
"android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
@@ -299,10 +303,12 @@
* are given an assigned number. Below shows the assigned number of Indicator added so far
* - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
* - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
*
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
"android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
@@ -432,15 +438,17 @@
* the state. Users can get the connection state of the profile
* from this intent.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -474,15 +482,14 @@
* {@link #STATE_DISCONNECTING} can be used to distinguish between the
* two scenarios.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
@SystemApi
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -502,6 +509,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHeadset service = mService;
@@ -521,6 +529,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHeadset service = mService;
@@ -540,6 +549,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getConnectionState(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -571,7 +581,12 @@
*/
@Deprecated
@SystemApi
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
final IBluetoothHeadset service = mService;
@@ -605,7 +620,11 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -638,7 +657,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
final IBluetoothHeadset service = mService;
@@ -688,7 +709,9 @@
* @param device Bluetooth device
* @return true if echo cancellation and/or noise reduction is supported, false otherwise
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
if (DBG) log("isNoiseReductionSupported()");
final IBluetoothHeadset service = mService;
@@ -709,7 +732,9 @@
* @param device Bluetooth device
* @return true if voice recognition is supported, false otherwise
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
if (DBG) log("isVoiceRecognitionSupported()");
final IBluetoothHeadset service = mService;
@@ -738,13 +763,17 @@
* audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
* in case of failure to establish the audio connection.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Bluetooth headset
* @return false if there is no headset connected, or the connected headset doesn't support
* voice recognition, or voice recognition is already started, or audio channel is occupied,
* or on error, true otherwise
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
final IBluetoothHeadset service = mService;
@@ -767,12 +796,13 @@
* If this function returns true, this intent will be broadcasted with
* {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Bluetooth headset
* @return false if there is no headset connected, or voice recognition has not started,
* or voice recognition has ended on this headset, or on error, true otherwise
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
final IBluetoothHeadset service = mService;
@@ -790,11 +820,12 @@
/**
* Check if Bluetooth SCO audio is connected.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Bluetooth headset
* @return true if SCO is connected, false otherwise or on error
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isAudioConnected(BluetoothDevice device) {
if (VDBG) log("isAudioConnected()");
final IBluetoothHeadset service = mService;
@@ -827,6 +858,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getAudioState(BluetoothDevice device) {
if (VDBG) log("getAudioState");
final IBluetoothHeadset service = mService;
@@ -853,6 +885,7 @@
* @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setAudioRouteAllowed(boolean allowed) {
if (VDBG) log("setAudioRouteAllowed");
final IBluetoothHeadset service = mService;
@@ -874,6 +907,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getAudioRouteAllowed() {
if (VDBG) log("getAudioRouteAllowed");
final IBluetoothHeadset service = mService;
@@ -897,6 +931,7 @@
* False to use SCO audio in normal manner
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setForceScoAudio(boolean forced) {
if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
final IBluetoothHeadset service = mService;
@@ -915,12 +950,13 @@
/**
* Check if at least one headset's SCO audio is connected or connecting
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @return true if at least one device's SCO audio is connected or connecting, false otherwise
* or on error
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isAudioOn() {
if (VDBG) log("isAudioOn()");
final IBluetoothHeadset service = mService;
@@ -955,6 +991,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connectAudio() {
final IBluetoothHeadset service = mService;
if (service != null && isEnabled()) {
@@ -982,6 +1019,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnectAudio() {
final IBluetoothHeadset service = mService;
if (service != null && isEnabled()) {
@@ -1018,7 +1056,12 @@
* - binder is dead or Bluetooth is disabled or other error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
@UnsupportedAppUsage
public boolean startScoUsingVirtualVoiceCall() {
if (DBG) log("startScoUsingVirtualVoiceCall()");
@@ -1048,7 +1091,12 @@
* - binder is dead or Bluetooth is disabled or other error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
@UnsupportedAppUsage
public boolean stopScoUsingVirtualVoiceCall() {
if (DBG) log("stopScoUsingVirtualVoiceCall()");
@@ -1075,6 +1123,10 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
int type, String name) {
final IBluetoothHeadset service = mService;
@@ -1095,6 +1147,10 @@
*
* @hide
*/
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
String number, int type) {
final IBluetoothHeadset service = mService;
@@ -1119,8 +1175,6 @@
*
* <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device Bluetooth headset.
* @param command A vendor-specific command.
* @param arg The argument that will be attached to the command.
@@ -1128,6 +1182,9 @@
* vendor-specific unsolicited result code, or on error. {@code true} otherwise.
* @throws IllegalArgumentException if {@code command} is {@code null}.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
String arg) {
if (DBG) {
@@ -1164,15 +1221,17 @@
* {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
* with the active device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device Remote Bluetooth Device, could be null if phone call audio should not be
* streamed to a headset
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ })
@UnsupportedAppUsage(trackingBug = 171933273)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) {
@@ -1201,7 +1260,9 @@
*/
@UnsupportedAppUsage(trackingBug = 171933273)
@Nullable
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getActiveDevice() {
if (VDBG) {
Log.d(TAG, "getActiveDevice");
@@ -1227,7 +1288,9 @@
* @return true if in-band ringing is enabled, false if in-band ringing is disabled
* @hide
*/
- @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isInbandRingingEnabled() {
if (DBG) {
log("isInbandRingingEnabled()");
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java
index e5b2a1e..092130d 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -19,6 +19,8 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
@@ -447,6 +449,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -473,6 +476,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -495,6 +499,7 @@
* @return list of connected devices; empty list if nothing is connected.
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHeadsetClient service =
@@ -519,6 +524,7 @@
* list if nothing matches the <code>states</code>
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHeadsetClient service =
@@ -542,6 +548,7 @@
* @return the state of connection of the device
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (VDBG) log("getConnectionState(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -569,7 +576,7 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -587,7 +594,7 @@
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -619,7 +626,9 @@
* @return priority of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -636,7 +645,9 @@
* @return connection policy of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -664,6 +675,7 @@
* #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
* is not supported.</p>
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
final IBluetoothHeadsetClient service =
@@ -688,6 +700,7 @@
* @return <code>true</code> if command has been issued successfully; <code>false</code>
* otherwise.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
String atCommand) {
if (DBG) log("sendVendorSpecificCommand()");
@@ -715,6 +728,7 @@
* #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
* is not supported.</p>
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
final IBluetoothHeadsetClient service =
@@ -736,6 +750,7 @@
* @param device remote device
* @return list of calls; empty list if none call exists
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
if (DBG) log("getCurrentCalls()");
final IBluetoothHeadsetClient service =
@@ -757,6 +772,7 @@
* @param device remote device
* @return bundle of AG indicators; null if device is not in CONNECTED state
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public Bundle getCurrentAgEvents(BluetoothDevice device) {
if (DBG) log("getCurrentCalls()");
final IBluetoothHeadsetClient service =
@@ -782,6 +798,7 @@
* otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean acceptCall(BluetoothDevice device, int flag) {
if (DBG) log("acceptCall()");
final IBluetoothHeadsetClient service =
@@ -804,6 +821,7 @@
* @return <code>true</code> if command has been issued successfully; <code>false</code>
* otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean holdCall(BluetoothDevice device) {
if (DBG) log("holdCall()");
final IBluetoothHeadsetClient service =
@@ -831,6 +849,7 @@
* supported.</p>
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean rejectCall(BluetoothDevice device) {
if (DBG) log("rejectCall()");
final IBluetoothHeadsetClient service =
@@ -862,6 +881,7 @@
* #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
* supported.</p>
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
if (DBG) log("terminateCall()");
final IBluetoothHeadsetClient service =
@@ -891,6 +911,7 @@
* #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
* supported.</p>
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean enterPrivateMode(BluetoothDevice device, int index) {
if (DBG) log("enterPrivateMode()");
final IBluetoothHeadsetClient service =
@@ -919,6 +940,7 @@
* #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
* is not supported.</p>
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean explicitCallTransfer(BluetoothDevice device) {
if (DBG) log("explicitCallTransfer()");
final IBluetoothHeadsetClient service =
@@ -943,6 +965,7 @@
* successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
* #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
if (DBG) log("dial()");
final IBluetoothHeadsetClient service =
@@ -968,6 +991,7 @@
* @return <code>true</code> if command has been issued successfully; <code>false</code>
* otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendDTMF(BluetoothDevice device, byte code) {
if (DBG) log("sendDTMF()");
final IBluetoothHeadsetClient service =
@@ -1089,6 +1113,7 @@
* @return <code>true</code> if command has been issued successfully; <code>false</code>
* otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connectAudio(BluetoothDevice device) {
final IBluetoothHeadsetClient service =
getService();
@@ -1114,6 +1139,7 @@
* @return <code>true</code> if command has been issued successfully; <code>false</code>
* otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnectAudio(BluetoothDevice device) {
final IBluetoothHeadsetClient service =
getService();
@@ -1136,6 +1162,7 @@
* @param device remote device
* @return bundle of AG features; null if no service or AG not connected
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public Bundle getCurrentAgFeatures(BluetoothDevice device) {
final IBluetoothHeadsetClient service =
getService();
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
index 5fd60e0..65f68a9 100644
--- a/core/java/android/bluetooth/BluetoothHealth.java
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -16,6 +16,10 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.os.ParcelFileDescriptor;
import android.util.Log;
@@ -111,8 +115,6 @@
* which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
* the callback is used to notify success or failure if the function returns true.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param name The friendly name associated with the application or configuration.
* @param dataType The dataType of the Source role of Health Profile to which the sink wants to
* connect to.
@@ -126,6 +128,10 @@
* {@link BluetoothDevice#createL2capChannel(int)}
*/
@Deprecated
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public boolean registerSinkAppConfiguration(String name, int dataType,
BluetoothHealthCallback callback) {
Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated");
@@ -136,8 +142,6 @@
* Unregister an application configuration that has been registered using
* {@link #registerSinkAppConfiguration}
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param config The health app configuration
* @return Success or failure.
*
@@ -147,6 +151,10 @@
* {@link BluetoothDevice#createL2capChannel(int)}
*/
@Deprecated
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated");
return false;
@@ -157,8 +165,6 @@
* This is an asynchronous call. If this function returns true, the callback
* associated with the application configuration will be called.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device The remote Bluetooth device.
* @param config The application configuration which has been registered using {@link
* #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
@@ -170,6 +176,10 @@
* {@link BluetoothDevice#createL2capChannel(int)}
*/
@Deprecated
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public boolean connectChannelToSource(BluetoothDevice device,
BluetoothHealthAppConfiguration config) {
Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated");
@@ -181,8 +191,6 @@
* This is an asynchronous call. If this function returns true, the callback
* associated with the application configuration will be called.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* @param device The remote Bluetooth device.
* @param config The application configuration which has been registered using {@link
* #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
@@ -195,6 +203,10 @@
* {@link BluetoothDevice#createL2capChannel(int)}
*/
@Deprecated
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public boolean disconnectChannel(BluetoothDevice device,
BluetoothHealthAppConfiguration config, int channelId) {
Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated");
@@ -205,8 +217,6 @@
* Get the file descriptor of the main channel associated with the remote device
* and application configuration.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* <p> Its the responsibility of the caller to close the ParcelFileDescriptor
* when done.
*
@@ -220,6 +230,10 @@
* {@link BluetoothDevice#createL2capChannel(int)}
*/
@Deprecated
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
BluetoothHealthAppConfiguration config) {
Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
@@ -229,8 +243,6 @@
/**
* Get the current connection state of the profile.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* This is not specific to any application configuration but represents the connection
* state of the local Bluetooth adapter with the remote device. This can be used
* by applications like status bar which would just like to know the state of the
@@ -241,6 +253,10 @@
* #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
*/
@Override
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public int getConnectionState(BluetoothDevice device) {
Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated");
return STATE_DISCONNECTED;
@@ -251,8 +267,6 @@
*
* <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* This is not specific to any application configuration but represents the connection
* state of the local Bluetooth adapter for this profile. This can be used
* by applications like status bar which would just like to know the state of the
@@ -261,6 +275,10 @@
* @return List of devices. The list will be empty on error.
*/
@Override
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public List<BluetoothDevice> getConnectedDevices() {
Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated");
return new ArrayList<>();
@@ -273,8 +291,7 @@
* <p> If none of the devices match any of the given states,
* an empty list will be returned.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
- * This is not specific to any application configuration but represents the connection
+ * <p>This is not specific to any application configuration but represents the connection
* state of the local Bluetooth adapter for this profile. This can be used
* by applications like status bar which would just like to know the state of the
* local adapter.
@@ -284,6 +301,10 @@
* @return List of devices. The list will be empty on error.
*/
@Override
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated");
return new ArrayList<>();
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index ff78825e..8ceeff5 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -22,6 +22,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -64,10 +67,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
@@ -81,11 +84,11 @@
* be null if no device is active. </li>
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
@@ -167,7 +170,10 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHearingAid service = getService();
@@ -225,6 +231,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHearingAid service = getService();
@@ -244,6 +251,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
@NonNull int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
@@ -264,6 +272,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @BluetoothProfile.BtProfileState int getConnectionState(
@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
@@ -295,14 +304,14 @@
* {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
* with the active device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
- *
* @param device the remote Bluetooth device. Could be null to clear
* the active device and stop streaming audio to a Bluetooth device.
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
@@ -330,7 +339,9 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getActiveDevices() {
if (VDBG) log("getActiveDevices()");
final IBluetoothHearingAid service = getService();
@@ -357,7 +368,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -376,7 +390,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -531,7 +548,7 @@
* @return SIDE_LEFT or SIDE_RIGHT
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
public int getDeviceSide(BluetoothDevice device) {
if (VDBG) {
log("getDeviceSide(" + device + ")");
@@ -557,7 +574,7 @@
* @return MODE_MONAURAL or MODE_BINAURAL
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
public int getDeviceMode(BluetoothDevice device) {
if (VDBG) {
log("getDeviceMode(" + device + ")");
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index 2baa738..c214d2b 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -21,6 +21,8 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
@@ -56,9 +58,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
* #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
* #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
@@ -436,6 +439,7 @@
/** {@inheritDoc} */
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
final IBluetoothHidDevice service = getService();
if (service != null) {
@@ -453,6 +457,7 @@
/** {@inheritDoc} */
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
final IBluetoothHidDevice service = getService();
if (service != null) {
@@ -470,6 +475,7 @@
/** {@inheritDoc} */
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
final IBluetoothHidDevice service = getService();
if (service != null) {
@@ -508,6 +514,7 @@
* object is required.
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean registerApp(
BluetoothHidDeviceAppSdpSettings sdp,
BluetoothHidDeviceAppQosSettings inQos,
@@ -553,6 +560,7 @@
*
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean unregisterApp() {
boolean result = false;
@@ -578,6 +586,7 @@
* @param data Report data, not including Report Id.
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
boolean result = false;
@@ -604,6 +613,7 @@
* @param data Report data, not including Report Id.
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
boolean result = false;
@@ -628,6 +638,7 @@
* @param error Error to be sent for SET_REPORT via HANDSHAKE.
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean reportError(BluetoothDevice device, byte error) {
boolean result = false;
@@ -651,6 +662,7 @@
* @return the current user name, or empty string if cannot get the name
* {@hide}
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public String getUserAppName() {
final IBluetoothHidDevice service = getService();
@@ -675,6 +687,7 @@
*
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(BluetoothDevice device) {
boolean result = false;
@@ -699,6 +712,7 @@
*
* @return true if the command is successfully sent; otherwise false.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
boolean result = false;
@@ -734,7 +748,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 9561d93..70e3809 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -21,6 +21,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
@@ -65,11 +68,11 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
@SuppressLint("ActionValue")
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
@@ -328,7 +331,7 @@
*/
@SystemApi
@Override
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHidHost service = getService();
@@ -350,6 +353,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothHidHost service = getService();
@@ -372,7 +376,7 @@
*/
@SystemApi
@Override
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
if (device == null) {
@@ -503,12 +507,13 @@
/**
* Initiate virtual unplug for a HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean virtualUnplug(BluetoothDevice device) {
if (DBG) log("virtualUnplug(" + device + ")");
final IBluetoothHidHost service = getService();
@@ -529,12 +534,13 @@
/**
* Send Get_Protocol_Mode command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getProtocolMode(BluetoothDevice device) {
if (VDBG) log("getProtocolMode(" + device + ")");
final IBluetoothHidHost service = getService();
@@ -553,12 +559,13 @@
/**
* Send Set_Protocol_Mode command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
if (DBG) log("setProtocolMode(" + device + ")");
final IBluetoothHidHost service = getService();
@@ -577,8 +584,6 @@
/**
* Send Get_Report command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @param reportType Report type
* @param reportId Report ID
@@ -586,6 +591,9 @@
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
int bufferSize) {
if (VDBG) {
@@ -608,14 +616,15 @@
/**
* Send Set_Report command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @param reportType Report type
* @param report Report receiving buffer size
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
final IBluetoothHidHost service = getService();
@@ -634,13 +643,14 @@
/**
* Send Send_Data command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @param report Report to send
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean sendData(BluetoothDevice device, String report) {
if (DBG) log("sendData(" + device + "), report=" + report);
final IBluetoothHidHost service = getService();
@@ -659,12 +669,13 @@
/**
* Send Get_Idle_Time command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean getIdleTime(BluetoothDevice device) {
if (DBG) log("getIdletime(" + device + ")");
final IBluetoothHidHost service = getService();
@@ -683,13 +694,14 @@
/**
* Send Set_Idle_Time command to the connected HID input device.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
- *
* @param device Remote Bluetooth Device
* @param idleTime Idle time to be set on HID Device
* @return false on immediate error, true otherwise
* @hide
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
final IBluetoothHidHost service = getService();
diff --git a/core/java/android/bluetooth/BluetoothLeAudio.java b/core/java/android/bluetooth/BluetoothLeAudio.java
index 3f00fa6..4f095f6 100644
--- a/core/java/android/bluetooth/BluetoothLeAudio.java
+++ b/core/java/android/bluetooth/BluetoothLeAudio.java
@@ -23,6 +23,9 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -65,10 +68,10 @@
* <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED =
"android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
@@ -82,11 +85,11 @@
* be null if no device is active. </li>
* </ul>
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED =
"android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED";
@@ -122,7 +125,6 @@
/**
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public void close() {
mProfileConnector.disconnect();
}
@@ -131,7 +133,6 @@
return mProfileConnector.getService();
}
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
protected void finalize() {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
@@ -154,7 +155,7 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect(@Nullable BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
try {
@@ -193,7 +194,7 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(@Nullable BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
try {
@@ -213,6 +214,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
try {
@@ -232,6 +234,7 @@
* {@inheritDoc}
*/
@Override
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
@NonNull int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
@@ -252,7 +255,9 @@
* {@inheritDoc}
*/
@Override
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
try {
@@ -289,7 +294,7 @@
* @return false on immediate error, true otherwise
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
try {
@@ -314,7 +319,7 @@
* @hide
*/
@NonNull
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
public List<BluetoothDevice> getActiveDevices() {
if (VDBG) log("getActiveDevices()");
try {
@@ -337,7 +342,7 @@
* @return group id that this device currently belongs to
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
public int getGroupId(@NonNull BluetoothDevice device) {
if (VDBG) log("getGroupId()");
try {
@@ -365,7 +370,10 @@
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -398,7 +406,7 @@
* @return connection policy of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
try {
diff --git a/core/java/android/bluetooth/BluetoothManager.java b/core/java/android/bluetooth/BluetoothManager.java
index d5c1c3e..a1e1b63 100644
--- a/core/java/android/bluetooth/BluetoothManager.java
+++ b/core/java/android/bluetooth/BluetoothManager.java
@@ -20,6 +20,8 @@
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.IBinder;
@@ -109,7 +111,9 @@
* {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED},
* {@link BluetoothProfile#STATE_DISCONNECTING}
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device, int profile) {
if (DBG) Log.d(TAG, "getConnectionState()");
@@ -136,7 +140,9 @@
* @param profile GATT or GATT_SERVER
* @return List of devices. The list will be empty on error.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices(int profile) {
if (DBG) Log.d(TAG, "getConnectedDevices");
if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
@@ -177,7 +183,9 @@
* {@link BluetoothProfile#STATE_DISCONNECTING},
* @return List of devices. The list will be empty on error.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) {
if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates");
@@ -210,6 +218,7 @@
* @param callback GATT server callback handler that will receive asynchronous callbacks.
* @return BluetoothGattServer instance
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback) {
@@ -229,6 +238,7 @@
* @return BluetoothGattServer instance
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback, boolean eatt_support) {
return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support));
@@ -249,6 +259,7 @@
* @return BluetoothGattServer instance
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback, int transport) {
return (openGattServer(context, callback, transport, false));
@@ -270,6 +281,7 @@
* @return BluetoothGattServer instance
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback, int transport, boolean eatt_support) {
if (context == null || callback == null) {
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 3554995..3e7b75a 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -93,7 +93,6 @@
mCloseGuard.open("close");
}
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
protected void finalize() {
if (mCloseGuard != null) {
mCloseGuard.warnIfOpen();
@@ -110,7 +109,6 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public void close() {
if (VDBG) log("close()");
mProfileConnector.disconnect();
@@ -128,6 +126,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getState() {
if (VDBG) log("getState()");
final IBluetoothMap service = getService();
@@ -152,6 +151,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
final IBluetoothMap service = getService();
@@ -175,6 +175,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
final IBluetoothMap service = getService();
@@ -211,6 +212,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothMap service = getService();
@@ -257,7 +259,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothMap service = getService();
@@ -280,6 +285,7 @@
*
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
final IBluetoothMap service = getService();
@@ -302,6 +308,7 @@
*
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
final IBluetoothMap service = getService();
@@ -328,7 +335,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -347,7 +357,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -378,7 +391,10 @@
* @return priority of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -396,7 +412,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothMap service = getService();
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index 0312a21..db74a90 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -192,6 +192,7 @@
* currently connected to the Map service.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isConnected(BluetoothDevice device) {
if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
final IBluetoothMapClient service = getService();
@@ -214,7 +215,10 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean connect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
final IBluetoothMapClient service = getService();
@@ -239,7 +243,10 @@
*
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean disconnect(BluetoothDevice device) {
if (DBG) Log.d(TAG, "disconnect(" + device + ")");
final IBluetoothMapClient service = getService();
@@ -261,6 +268,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) Log.d(TAG, "getConnectedDevices()");
final IBluetoothMapClient service = getService();
@@ -283,6 +291,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
final IBluetoothMapClient service = getService();
@@ -305,6 +314,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
final IBluetoothMapClient service = getService();
@@ -331,7 +341,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -349,7 +362,10 @@
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -380,7 +396,10 @@
* @return priority of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public int getPriority(BluetoothDevice device) {
if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -397,7 +416,10 @@
* @return connection policy of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
final IBluetoothMapClient service = getService();
@@ -427,7 +449,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.SEND_SMS)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.SEND_SMS,
+ })
public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
@NonNull String message, @Nullable PendingIntent sentIntent,
@Nullable PendingIntent deliveredIntent) {
@@ -459,6 +484,10 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.SEND_SMS,
+ })
public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
PendingIntent sentIntent, PendingIntent deliveredIntent) {
if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
@@ -481,6 +510,10 @@
* @return true if the message is enqueued, false on error
* @hide
*/
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.READ_SMS,
+ })
public boolean getUnreadMessages(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
final IBluetoothMapClient service = getService();
@@ -503,6 +536,7 @@
* MapSupportedFeatures field is set. False is returned otherwise.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isUploadingSupported(BluetoothDevice device) {
final IBluetoothMapClient service = getService();
try {
@@ -530,7 +564,10 @@
* @return <code>true</code> if request has been sent, <code>false</code> on error
* @hide
*/
- @RequiresPermission(Manifest.permission.READ_SMS)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.READ_SMS,
+ })
public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
final IBluetoothMapClient service = getService();
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index ecd718c..b3924b1f 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -22,6 +22,8 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -74,10 +76,11 @@
*
* <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or
* {@link #LOCAL_PANU_ROLE}
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
@SuppressLint("ActionValue")
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
@@ -102,9 +105,10 @@
*
* <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
* {@link #TETHERING_STATE_ON}
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_TETHERING_STATE_CHANGED =
"android.bluetooth.action.TETHERING_STATE_CHANGED";
@@ -236,6 +240,10 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothPan service = getService();
@@ -274,6 +282,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothPan service = getService();
@@ -302,7 +311,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -330,7 +342,10 @@
*/
@SystemApi
@Override
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothPan service = getService();
@@ -351,7 +366,12 @@
* @hide
*/
@Override
- @RequiresPermission(Manifest.permission.BLUETOOTH)
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (VDBG) log("getDevicesMatchingStates()");
final IBluetoothPan service = getService();
@@ -396,7 +416,11 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ android.Manifest.permission.TETHER_PRIVILEGED,
+ })
public void setBluetoothTethering(boolean value) {
String pkgName = mContext.getOpPackageName();
if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
@@ -417,7 +441,7 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isTetheringOn() {
if (VDBG) log("isTetheringOn()");
final IBluetoothPan service = getService();
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index 6e5c45f..6c2e5bf 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -22,6 +22,8 @@
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -82,8 +84,6 @@
* can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
* {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
* {@link BluetoothProfile#STATE_DISCONNECTING}.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
*
* @hide
*/
@@ -142,6 +142,7 @@
doBind();
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
boolean doBind() {
synchronized (mConnection) {
try {
@@ -216,6 +217,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
log("getConnectedDevices()");
final IBluetoothPbap service = mService;
@@ -262,6 +264,7 @@
* @hide
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
final IBluetoothPbap service = mService;
@@ -294,7 +297,10 @@
* @hide
*/
@SystemApi
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -324,6 +330,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
log("disconnect()");
final IBluetoothPbap service = mService;
diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java
index f356da1..2c8fbc2 100644
--- a/core/java/android/bluetooth/BluetoothPbapClient.java
+++ b/core/java/android/bluetooth/BluetoothPbapClient.java
@@ -19,6 +19,7 @@
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -160,6 +161,7 @@
* @return list of connected devices
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) {
log("getConnectedDevices()");
@@ -185,6 +187,7 @@
* @return list of matching devices
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) {
log("getDevicesMatchingStates()");
@@ -210,6 +213,7 @@
* @return device connection state
*/
@Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (DBG) {
log("getConnectionState(" + device + ")");
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 201d6c4..70053ee 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-
package android.bluetooth;
-import android.Manifest;
import android.annotation.IntDef;
-import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
@@ -300,7 +297,6 @@
*
* @return List of devices. The list will be empty on error.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
public List<BluetoothDevice> getConnectedDevices();
/**
@@ -314,7 +310,6 @@
* #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
* @return List of devices. The list will be empty on error.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
/**
@@ -324,7 +319,6 @@
* @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link
* #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH)
@BtProfileState int getConnectionState(BluetoothDevice device);
/**
diff --git a/core/java/android/bluetooth/BluetoothProfileConnector.java b/core/java/android/bluetooth/BluetoothProfileConnector.java
index 863fd36..12abcc4 100644
--- a/core/java/android/bluetooth/BluetoothProfileConnector.java
+++ b/core/java/android/bluetooth/BluetoothProfileConnector.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -78,6 +80,7 @@
mServiceName = serviceName;
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
private boolean doBind() {
synchronized (mConnection) {
if (mService == null) {
diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java
index 0d70dbd..c85494c 100644
--- a/core/java/android/bluetooth/BluetoothSap.java
+++ b/core/java/android/bluetooth/BluetoothSap.java
@@ -18,6 +18,9 @@
import android.Manifest;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
@@ -61,11 +64,11 @@
* {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
* {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- *
* @hide
*/
+ @RequiresLegacyBluetoothPermission
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
@@ -140,6 +143,7 @@
* connected to the Sap service.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public int getState() {
if (VDBG) log("getState()");
final IBluetoothSap service = getService();
@@ -163,6 +167,7 @@
* this proxy object is not connected to the Sap service.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
final IBluetoothSap service = getService();
@@ -186,6 +191,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
final IBluetoothSap service = getService();
@@ -221,6 +227,7 @@
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothSap service = getService();
@@ -242,6 +249,7 @@
* @return list of connected devices
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothSap service = getService();
@@ -263,6 +271,7 @@
* @return list of matching devices
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
final IBluetoothSap service = getService();
@@ -284,6 +293,7 @@
* @return device connection state
* @hide
*/
+ @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
final IBluetoothSap service = getService();
@@ -310,7 +320,10 @@
* @return true if priority is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setPriority(BluetoothDevice device, int priority) {
if (DBG) log("setPriority(" + device + ", " + priority + ")");
return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
@@ -328,7 +341,10 @@
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public boolean setConnectionPolicy(BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
@@ -359,7 +375,10 @@
* @return priority of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
@@ -376,7 +395,10 @@
* @return connection policy of the device
* @hide
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothSap service = getService();
diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java
index 5c1bcaf..5082235 100644
--- a/core/java/android/bluetooth/BluetoothServerSocket.java
+++ b/core/java/android/bluetooth/BluetoothServerSocket.java
@@ -62,9 +62,6 @@
* safe. In particular, {@link #close} will always immediately abort ongoing
* operations and close the server socket.
*
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using Bluetooth, read the
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 65381db..ef88147 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.LocalSocket;
import android.os.Build;
@@ -70,9 +72,6 @@
* safe. In particular, {@link #close} will always immediately abort ongoing
* operations and close the socket.
*
- * <p class="note"><strong>Note:</strong>
- * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
- *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about using Bluetooth, read the
@@ -199,6 +198,7 @@
* @throws IOException On error, for example Bluetooth not available, or insufficient
* privileges
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
/*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin)
throws IOException {
@@ -386,6 +386,7 @@
*
* @throws IOException on error, for example connection failure
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void connect() throws IOException {
if (mDevice == null) throw new IOException("Connect is called on null device");
@@ -427,6 +428,7 @@
* Currently returns unix errno instead of throwing IOException,
* so that BluetoothAdapter can check the error code for EADDRINUSE
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
/*package*/ int bindListen() {
int ret;
if (mSocketState == SocketState.CLOSED) return EBADFD;
@@ -682,6 +684,7 @@
* connection. This function is currently used for testing only.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void requestMaximumTxDataLength() throws IOException {
if (mDevice == null) {
throw new IOException("requestMaximumTxDataLength is called on null device");
diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java
index 08d694e..d6868e0 100644
--- a/core/java/android/bluetooth/OobData.java
+++ b/core/java/android/bluetooth/OobData.java
@@ -830,7 +830,7 @@
@Nullable
@SystemApi
public byte[] getLeAppearance() {
- return mLeTemporaryKey;
+ return mLeAppearance;
}
/**
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java
new file mode 100644
index 0000000..c508c2c
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ * this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE}
+ * permission which can be gained with
+ * {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothAdvertisePermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java
new file mode 100644
index 0000000..e159eaa
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ * this requires the {@link Manifest.permission#BLUETOOTH_CONNECT}
+ * permission which can be gained with
+ * {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothConnectPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java
new file mode 100644
index 0000000..2bb3204
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java
@@ -0,0 +1,41 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc In addition, this requires either the
+ * {@link Manifest.permission#ACCESS_FINE_LOCATION}
+ * permission or a strong assertion that you will never derive the
+ * physical location of the device. You can make this assertion by
+ * declaring {@code usesPermissionFlags="neverForLocation"} on the
+ * relevant {@code <uses-permission>} manifest tag, but it may
+ * restrict the types of Bluetooth devices you can interact with.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothLocationPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java
new file mode 100644
index 0000000..800ff39
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
+ * this requires the {@link Manifest.permission#BLUETOOTH_SCAN}
+ * permission which can be gained with
+ * {@link android.app.Activity#requestPermissions(String[], int)}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresBluetoothScanPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java
new file mode 100644
index 0000000..9adf695
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
+ * requires the {@link Manifest.permission#BLUETOOTH_ADMIN}
+ * permission which can be gained with a simple
+ * {@code <uses-permission>} manifest tag.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresLegacyBluetoothAdminPermission {
+}
diff --git a/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java
new file mode 100644
index 0000000..79621c3
--- /dev/null
+++ b/core/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java
@@ -0,0 +1,39 @@
+/*
+ * 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.bluetooth.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.Manifest;
+import android.os.Build;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
+ * requires the {@link Manifest.permission#BLUETOOTH} permission
+ * which can be gained with a simple {@code <uses-permission>}
+ * manifest tag.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, FIELD})
+public @interface RequiresLegacyBluetoothPermission {
+}
diff --git a/core/java/android/bluetooth/le/AdvertisingSet.java b/core/java/android/bluetooth/le/AdvertisingSet.java
index 1df35e1..54a18e6 100644
--- a/core/java/android/bluetooth/le/AdvertisingSet.java
+++ b/core/java/android/bluetooth/le/AdvertisingSet.java
@@ -16,9 +16,12 @@
package android.bluetooth.le;
+import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.os.RemoteException;
import android.util.Log;
@@ -27,9 +30,6 @@
* <p>
* To get an instance of {@link AdvertisingSet}, call the
* {@link BluetoothLeAdvertiser#startAdvertisingSet} method.
- * <p>
- * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
*
* @see AdvertiseData
*/
@@ -58,8 +58,6 @@
/**
* Enables Advertising. This method returns immediately, the operation status is
* delivered through {@code callback.onAdvertisingEnabled()}.
- * <p>
- * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param enable whether the advertising should be enabled (true), or disabled (false)
* @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
@@ -68,6 +66,9 @@
* controller shall attempt to send prior to terminating the extended advertising, even if the
* duration has not expired. Valid range is from 1 to 255.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void enableAdvertising(boolean enable, int duration,
int maxExtendedAdvertisingEvents) {
try {
@@ -90,6 +91,9 @@
* three bytes will be added for flags. If the update takes place when the advertising set is
* enabled, the data can be maximum 251 bytes long.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setAdvertisingData(AdvertiseData advertiseData) {
try {
mGatt.setAdvertisingData(mAdvertiserId, advertiseData);
@@ -107,6 +111,9 @@
* exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place
* when the advertising set is enabled, the data can be maximum 251 bytes long.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setScanResponseData(AdvertiseData scanResponse) {
try {
mGatt.setScanResponseData(mAdvertiserId, scanResponse);
@@ -122,6 +129,9 @@
*
* @param parameters advertising set parameters.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setAdvertisingParameters(AdvertisingSetParameters parameters) {
try {
mGatt.setAdvertisingParameters(mAdvertiserId, parameters);
@@ -135,6 +145,9 @@
* periodic advertising is not enabled. This method returns immediately, the operation
* status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
try {
mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters);
@@ -153,6 +166,9 @@
* BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the
* periodic advertising is enabled for this set, the data can be maximum 251 bytes long.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setPeriodicAdvertisingData(AdvertiseData periodicData) {
try {
mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData);
@@ -168,6 +184,9 @@
* @param enable whether the periodic advertising should be enabled (true), or disabled
* (false).
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void setPeriodicAdvertisingEnabled(boolean enable) {
try {
mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable);
@@ -181,10 +200,9 @@
* This method is exposed only for Bluetooth PTS tests, no app or system service
* should ever use it.
*
- * This method requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission.
- *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void getOwnAddress() {
try {
mGatt.getOwnAddress(mAdvertiserId);
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5f166f4..de11869 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -16,11 +16,15 @@
package android.bluetooth.le;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelUuid;
@@ -38,9 +42,6 @@
* <p>
* To get an instance of {@link BluetoothLeAdvertiser}, call the
* {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
- * <p>
- * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
*
* @see AdvertiseData
*/
@@ -81,13 +82,17 @@
/**
* Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
* Returns immediately, the operation status is delivered through {@code callback}.
- * <p>
- * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @param settings Settings for Bluetooth LE advertising.
* @param advertiseData Advertisement data to be broadcasted.
* @param callback Callback for advertising status.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertising(AdvertiseSettings settings,
AdvertiseData advertiseData, final AdvertiseCallback callback) {
startAdvertising(settings, advertiseData, null, callback);
@@ -98,14 +103,18 @@
* operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
* active scan request. This method returns immediately, the operation status is delivered
* through {@code callback}.
- * <p>
- * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
* @param settings Settings for Bluetooth LE advertising.
* @param advertiseData Advertisement data to be advertised in advertisement packet.
* @param scanResponse Scan response associated with the advertisement data.
* @param callback Callback for advertising status.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertising(AdvertiseSettings settings,
AdvertiseData advertiseData, AdvertiseData scanResponse,
final AdvertiseCallback callback) {
@@ -160,9 +169,11 @@
}
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
return new AdvertisingSetCallback() {
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
int status) {
if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
@@ -175,6 +186,7 @@
/* Legacy advertiser is disabled on timeout */
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
int status) {
if (enabled) {
@@ -192,11 +204,12 @@
/**
* Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
* {@link BluetoothLeAdvertiser#startAdvertising}.
- * <p>
- * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void stopAdvertising(final AdvertiseCallback callback) {
synchronized (mLegacyAdvertisers) {
if (callback == null) {
@@ -232,6 +245,12 @@
* size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
* feature is made when it's not supported by the controller.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertisingSet(AdvertisingSetParameters parameters,
AdvertiseData advertiseData, AdvertiseData scanResponse,
PeriodicAdvertisingParameters periodicParameters,
@@ -262,6 +281,12 @@
* size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
* feature is made when it's not supported by the controller.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertisingSet(AdvertisingSetParameters parameters,
AdvertiseData advertiseData, AdvertiseData scanResponse,
PeriodicAdvertisingParameters periodicParameters,
@@ -297,6 +322,12 @@
* size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
* feature is made when it's not supported by the controller.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertisingSet(AdvertisingSetParameters parameters,
AdvertiseData advertiseData, AdvertiseData scanResponse,
PeriodicAdvertisingParameters periodicParameters,
@@ -337,6 +368,12 @@
* maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended
* Advertising
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_ADVERTISE,
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ })
public void startAdvertisingSet(AdvertisingSetParameters parameters,
AdvertiseData advertiseData, AdvertiseData scanResponse,
PeriodicAdvertisingParameters periodicParameters,
@@ -445,6 +482,9 @@
* Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
* BluetoothLeAdvertiser#startAdvertisingSet}.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothAdvertisePermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
public void stopAdvertisingSet(AdvertisingSetCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
@@ -476,6 +516,7 @@
}
// Compute the size of advertisement data or scan resp
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
if (data == null) return 0;
// Flags field is omitted if the advertising is not connectable.
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index 2601cd4..4271a90 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -20,12 +20,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.content.AttributionSource;
import android.os.Handler;
import android.os.Looper;
@@ -45,9 +49,6 @@
* <p>
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
* {@link BluetoothLeScanner}.
- * <p>
- * <b>Note:</b> Most of the scan methods here require
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @see ScanFilter
*/
@@ -117,7 +118,10 @@
* @param callback Callback used to deliver scan results.
* @throws IllegalArgumentException If {@code callback} is null.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startScan(final ScanCallback callback) {
startScan(null, new ScanSettings.Builder().build(), callback);
}
@@ -139,7 +143,10 @@
* @param callback Callback used to deliver scan results.
* @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startScan(List<ScanFilter> filters, ScanSettings settings,
final ScanCallback callback) {
startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null);
@@ -168,7 +175,10 @@
* could not be sent.
* @see #stopScan(PendingIntent)
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings,
@NonNull PendingIntent callbackIntent) {
return startScan(filters,
@@ -186,8 +196,13 @@
* @hide
*/
@SystemApi
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
@RequiresPermission(allOf = {
- Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+ android.Manifest.permission.BLUETOOTH_SCAN,
+ android.Manifest.permission.UPDATE_DEVICE_STATS
+ })
public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
}
@@ -204,13 +219,20 @@
* @hide
*/
@SystemApi
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
@RequiresPermission(allOf = {
- Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+ android.Manifest.permission.BLUETOOTH_SCAN,
+ android.Manifest.permission.UPDATE_DEVICE_STATS
+ })
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings,
final WorkSource workSource, final ScanCallback callback) {
startScan(filters, settings, workSource, callback, null, null);
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
private int startScan(List<ScanFilter> filters, ScanSettings settings,
final WorkSource workSource, final ScanCallback callback,
final PendingIntent callbackIntent,
@@ -268,7 +290,9 @@
*
* @param callback
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void stopScan(ScanCallback callback) {
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
synchronized (mLeScanClients) {
@@ -289,7 +313,9 @@
* @param callbackIntent The PendingIntent that was used to start the scan.
* @see #startScan(List, ScanSettings, PendingIntent)
*/
- @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void stopScan(PendingIntent callbackIntent) {
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
IBluetoothGatt gatt;
@@ -308,6 +334,9 @@
* @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
* used to start scan.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void flushPendingScanResults(ScanCallback callback) {
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
if (callback == null) {
@@ -328,6 +357,7 @@
* @hide
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
final ScanCallback callback) {
int filterSize = truncatedFilters.size();
@@ -382,6 +412,8 @@
mResultStorages = resultStorages;
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void startRegistration() {
synchronized (this) {
// Scan stopped.
@@ -409,6 +441,8 @@
}
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void stopLeScan() {
synchronized (this) {
if (mScannerId <= 0) {
@@ -425,6 +459,8 @@
}
}
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
void flushPendingBatchResults() {
synchronized (this) {
if (mScannerId <= 0) {
@@ -443,6 +479,7 @@
* Application interface registered - app is ready to go
*/
@Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void onScannerRegistered(int status, int scannerId) {
Log.d(TAG, "onScannerRegistered() - status=" + status
+ " scannerId=" + scannerId + " mScannerId=" + mScannerId);
@@ -595,6 +632,7 @@
return true;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) {
final int callbackType = settings.getCallbackType();
if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
diff --git a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
index 0f1a8e9..9ea6c48 100644
--- a/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
+++ b/core/java/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -16,10 +16,14 @@
package android.bluetooth.le;
+import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothManager;
+import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
+import android.bluetooth.annotations.RequiresBluetoothScanPermission;
+import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
@@ -35,9 +39,6 @@
* <p>
* Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
* instance of {@link PeriodicAdvertisingManager}.
- * <p>
- * <b>Note:</b> Most of the methods here require
- * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @hide
*/
@@ -89,6 +90,10 @@
* @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
* {@code timeout} is invalid or {@code callback} is null.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void registerSync(ScanResult scanResult, int skip, int timeout,
PeriodicAdvertisingCallback callback) {
registerSync(scanResult, skip, timeout, callback, null);
@@ -113,6 +118,10 @@
* @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
* {@code timeout} is invalid or {@code callback} is null.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresBluetoothLocationPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void registerSync(ScanResult scanResult, int skip, int timeout,
PeriodicAdvertisingCallback callback, Handler handler) {
if (callback == null) {
@@ -170,6 +179,9 @@
* @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
* callback.
*/
+ @RequiresLegacyBluetoothAdminPermission
+ @RequiresBluetoothScanPermission
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
public void unregisterSync(PeriodicAdvertisingCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback can't be null");
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 09ac810..318913f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2208,6 +2208,17 @@
}
/**
+ * Like {@link #sendBroadcastMultiplePermissions(Intent, String[])}, but also allows
+ * specification of a list of excluded permissions. This allows sending a broadcast to an
+ * app that has the permissions in `receiverPermissions` but not `excludedPermissions`.
+ * @hide
+ */
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+ throw new RuntimeException("Not implemented. Must override in a subclass.");
+ }
+
+ /**
* Broadcast the given intent to all interested BroadcastReceivers, allowing
* an array of required permissions to be enforced. This call is asynchronous; it returns
* immediately, and you will continue executing while the receivers are run. No results are
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 8936d0c..dddcbea 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -493,6 +493,13 @@
/** @hide */
@Override
+ public void sendBroadcastMultiplePermissions(@NonNull Intent intent,
+ @NonNull String[] receiverPermissions, @Nullable String[] excludedPermissions) {
+ mBase.sendBroadcastMultiplePermissions(intent, receiverPermissions, excludedPermissions);
+ }
+
+ /** @hide */
+ @Override
public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
String[] receiverPermissions) {
mBase.sendBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5ff1124..86a8a9d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8923,6 +8923,8 @@
private final @ParseFlags int mFlags;
private AssetManager mCachedAssetManager;
+ private ApkAssets mBaseApkAssets;
+
DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
mBaseCodePath = pkg.baseCodePath;
mSplitCodePaths = pkg.splitCodePaths;
@@ -8953,9 +8955,11 @@
ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
? mSplitCodePaths.length : 0) + 1];
+ mBaseApkAssets = loadApkAssets(mBaseCodePath, mFlags);
+
// Load the base.
int splitIdx = 0;
- apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
+ apkAssets[splitIdx++] = mBaseApkAssets;
// Load any splits.
if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
@@ -8982,6 +8986,11 @@
public void close() throws Exception {
IoUtils.closeQuietly(mCachedAssetManager);
}
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mBaseApkAssets;
+ }
}
/**
@@ -9085,5 +9094,10 @@
IoUtils.closeQuietly(assets);
}
}
+
+ @Override
+ public ApkAssets getBaseApkAssets() {
+ return mCachedSplitApks[0][0];
+ }
}
}
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index a1ffc0c..0fc6b2b 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -383,10 +383,9 @@
}
try {
- final AssetManager assets = assetLoader.getBaseAssetManager();
final File baseApk = new File(lite.getBaseApkPath());
final ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
- lite.getPath(), assets, flags);
+ lite.getPath(), assetLoader, flags);
if (result.isError()) {
return input.error(result);
}
@@ -442,7 +441,7 @@
final ParseResult<ParsingPackage> result = parseBaseApk(input,
apkFile,
apkFile.getCanonicalPath(),
- assetLoader.getBaseAssetManager(), flags);
+ assetLoader, flags);
if (result.isError()) {
return input.error(result);
}
@@ -458,7 +457,8 @@
}
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,
- String codePath, AssetManager assets, int flags) {
+ String codePath, SplitAssetLoader assetLoader, int flags)
+ throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
String volumeUuid = null;
@@ -469,6 +469,7 @@
if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
+ final AssetManager assets = assetLoader.getBaseAssetManager();
final int cookie = assets.findCookieForPath(apkPath);
if (cookie == 0) {
return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST,
@@ -500,12 +501,19 @@
}
}
- ApkAssets apkAssets = assets.getApkAssets()[0];
- if (apkAssets.definesOverlayable()) {
+ ApkAssets apkAssets = assetLoader.getBaseApkAssets();
+ boolean definesOverlayable = false;
+ try {
+ definesOverlayable = apkAssets.definesOverlayable();
+ } catch (IOException ignored) {
+ // Will fail if there's no packages in the ApkAssets, which can be treated as false
+ }
+
+ if (definesOverlayable) {
SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();
int size = packageNames.size();
for (int index = 0; index < size; index++) {
- String packageName = packageNames.get(index);
+ String packageName = packageNames.valueAt(index);
Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);
if (overlayableToActor != null && !overlayableToActor.isEmpty()) {
for (String overlayable : overlayableToActor.keySet()) {
@@ -2799,7 +2807,15 @@
}
}
+ @SuppressWarnings("AndroidFrameworkCompatChange")
private void convertSplitPermissions(ParsingPackage pkg) {
+ // STOPSHIP(b/183905675): REMOVE THIS TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK
+ if ("com.android.chrome".equals(pkg.getPackageName())
+ && pkg.getVersionCode() <= 445500399
+ && pkg.getTargetSdkVersion() > Build.VERSION_CODES.R) {
+ pkg.setTargetSdkVersion(Build.VERSION_CODES.R);
+ }
+
final int listSize = mSplitPermissionInfos.size();
for (int is = 0; is < listSize; is++) {
final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is);
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index f3caf60..c1a8396 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -43,6 +43,8 @@
private final @ParseFlags int mFlags;
private AssetManager mCachedAssetManager;
+ private ApkAssets mBaseApkAssets;
+
public DefaultSplitAssetLoader(PackageLite pkg, @ParseFlags int flags) {
mBaseApkPath = pkg.getBaseApkPath();
mSplitApkPaths = pkg.getSplitApkPaths();
@@ -76,7 +78,7 @@
// Load the base.
int splitIdx = 0;
- apkAssets[splitIdx++] = loadApkAssets(mBaseApkPath, mFlags);
+ apkAssets[splitIdx++] = mBaseApkAssets = loadApkAssets(mBaseApkPath, mFlags);
// Load any splits.
if (!ArrayUtils.isEmpty(mSplitApkPaths)) {
@@ -100,6 +102,11 @@
}
@Override
+ public ApkAssets getBaseApkAssets() {
+ return mBaseApkAssets;
+ }
+
+ @Override
public void close() throws Exception {
IoUtils.closeQuietly(mCachedAssetManager);
}
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 523ca40..e5c2158 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -128,6 +128,11 @@
}
@Override
+ public ApkAssets getBaseApkAssets() {
+ return mCachedSplitApks[0][0];
+ }
+
+ @Override
public void close() throws Exception {
for (AssetManager assets : mCachedAssetManagers) {
IoUtils.closeQuietly(assets);
diff --git a/core/java/android/content/pm/split/SplitAssetLoader.java b/core/java/android/content/pm/split/SplitAssetLoader.java
index 108fb95..7584e15f 100644
--- a/core/java/android/content/pm/split/SplitAssetLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetLoader.java
@@ -16,6 +16,7 @@
package android.content.pm.split;
import android.content.pm.PackageParser;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
/**
@@ -27,4 +28,6 @@
public interface SplitAssetLoader extends AutoCloseable {
AssetManager getBaseAssetManager() throws PackageParser.PackageParserException;
AssetManager getSplitAssetManager(int splitIdx) throws PackageParser.PackageParserException;
+
+ ApkAssets getBaseApkAssets();
}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index adc668f..77bd147 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -32,7 +32,9 @@
import com.android.internal.util.CollectionUtils;
+import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -350,6 +352,8 @@
*
* The set will be ordered from lowest to highest priority.
*
+ * @param domain The host to query for. An invalid domain will result in an empty set.
+ *
* @hide
*/
@SystemApi
@@ -357,11 +361,11 @@
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
public SortedSet<DomainOwner> getOwnersForDomain(@NonNull String domain) {
try {
+ Objects.requireNonNull(domain);
final List<DomainOwner> orderedList = mDomainVerificationManager.getOwnersForDomain(
domain, mContext.getUserId());
SortedSet<DomainOwner> set = new TreeSet<>(
- (first, second) -> Integer.compare(orderedList.indexOf(first),
- orderedList.indexOf(second)));
+ Comparator.comparingInt(orderedList::indexOf));
set.addAll(orderedList);
return set;
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 3c11d8e..bc2dcb3 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -571,10 +571,10 @@
}
int sensorHandle = (sensor == null) ? -1 : sensor.getHandle();
- if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
- && rate > CAPPED_SAMPLING_RATE_LEVEL
+ if (rate > CAPPED_SAMPLING_RATE_LEVEL
&& mIsPackageDebuggable
- && !mHasHighSamplingRateSensorsPermission) {
+ && !mHasHighSamplingRateSensorsPermission
+ && Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)) {
throw new SecurityException("To use the sampling rate level " + rate
+ ", app needs to declare the normal permission"
+ " HIGH_SAMPLING_RATE_SENSORS.");
@@ -782,10 +782,10 @@
Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
if (mNativeSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
- if (Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)
- && rateUs < CAPPED_SAMPLING_PERIOD_US
+ if (rateUs < CAPPED_SAMPLING_PERIOD_US
&& mManager.mIsPackageDebuggable
- && !mManager.mHasHighSamplingRateSensorsPermission) {
+ && !mManager.mHasHighSamplingRateSensorsPermission
+ && Compatibility.isChangeEnabled(CHANGE_ID_SAMPLING_RATE_SENSORS_PERMISSION)) {
throw new SecurityException("To use the sampling rate of " + rateUs
+ " microseconds, app needs to declare the normal permission"
+ " HIGH_SAMPLING_RATE_SENSORS.");
diff --git a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
index 62d727c..1268658 100644
--- a/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl
@@ -16,11 +16,9 @@
package android.hardware.biometrics;
-import android.hardware.biometrics.BiometricSourceType;
-
/**
* @hide
*/
oneway interface IBiometricEnabledOnKeyguardCallback {
- void onChanged(in BiometricSourceType type, boolean enabled, int userId);
+ void onChanged(boolean enabled, int userId);
}
\ No newline at end of file
diff --git a/core/java/android/hardware/display/DeviceProductInfo.java b/core/java/android/hardware/display/DeviceProductInfo.java
index 11c426a..835b3fd 100644
--- a/core/java/android/hardware/display/DeviceProductInfo.java
+++ b/core/java/android/hardware/display/DeviceProductInfo.java
@@ -74,12 +74,26 @@
Integer modelYear,
ManufactureDate manufactureDate,
int connectionToSinkType) {
- this.mName = name;
- this.mManufacturerPnpId = manufacturerPnpId;
- this.mProductId = productId;
- this.mModelYear = modelYear;
- this.mManufactureDate = manufactureDate;
- this.mConnectionToSinkType = connectionToSinkType;
+ mName = name;
+ mManufacturerPnpId = manufacturerPnpId;
+ mProductId = productId;
+ mModelYear = modelYear;
+ mManufactureDate = manufactureDate;
+ mConnectionToSinkType = connectionToSinkType;
+ }
+
+ public DeviceProductInfo(
+ @Nullable String name,
+ @NonNull String manufacturerPnpId,
+ @NonNull String productId,
+ @IntRange(from = 1990) int modelYear,
+ @ConnectionToSinkType int connectionToSinkType) {
+ mName = name;
+ mManufacturerPnpId = Objects.requireNonNull(manufacturerPnpId);
+ mProductId = Objects.requireNonNull(productId);
+ mModelYear = modelYear;
+ mManufactureDate = null;
+ mConnectionToSinkType = connectionToSinkType;
}
private DeviceProductInfo(Parcel in) {
@@ -100,6 +114,9 @@
}
/**
+ * Returns the Manufacturer Plug and Play ID. This ID identifies the manufacture according to
+ * the list: https://uefi.org/PNP_ID_List. It consist of 3 characters, each character
+ * is an uppercase letter (A-Z).
* @return Manufacturer Plug and Play ID.
*/
@NonNull
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 5f65d46..662ebb3 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -78,10 +78,8 @@
/**
* An IPsec VPN created by the built-in LegacyVpnRunner.
- * @deprecated new Android devices should use VPN_TYPE_PLATFORM instead.
* @hide
*/
- @Deprecated
@SystemApi(client = MODULE_LIBRARIES)
public static final int TYPE_VPN_LEGACY = 3;
@@ -418,4 +416,4 @@
throw e.rethrowFromSystemServer();
}
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index d5cc01a..cb9a3e4 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -24,7 +24,7 @@
import android.nfc.TechListParcel;
import android.nfc.IAppCallback;
import android.nfc.INfcAdapterExtras;
-import android.nfc.INfcControllerAlwaysOnStateCallback;
+import android.nfc.INfcControllerAlwaysOnListener;
import android.nfc.INfcTag;
import android.nfc.INfcCardEmulation;
import android.nfc.INfcFCardEmulation;
@@ -76,6 +76,6 @@
boolean setControllerAlwaysOn(boolean value);
boolean isControllerAlwaysOn();
boolean isControllerAlwaysOnSupported();
- void registerControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
- void unregisterControllerAlwaysOnStateCallback(in INfcControllerAlwaysOnStateCallback callback);
+ void registerControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
+ void unregisterControllerAlwaysOnListener(in INfcControllerAlwaysOnListener listener);
}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
similarity index 87%
rename from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
rename to core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
index 1e4fdd7..1bb7680 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl
@@ -19,11 +19,11 @@
/**
* @hide
*/
-oneway interface INfcControllerAlwaysOnStateCallback {
+oneway interface INfcControllerAlwaysOnListener {
/**
* Called whenever the controller always on state changes
*
* @param isEnabled true if the state is enabled, false otherwise
*/
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+ void onControllerAlwaysOnChanged(boolean isEnabled);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index bbf802c..64c1211 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -67,7 +67,7 @@
public final class NfcAdapter {
static final String TAG = "NFC";
- private final NfcControllerAlwaysOnStateListener mControllerAlwaysOnStateListener;
+ private final NfcControllerAlwaysOnListener mControllerAlwaysOnListener;
/**
* Intent to start an activity when a tag with NDEF payload is discovered.
@@ -418,19 +418,19 @@
}
/**
- * A callback to be invoked when NFC controller always on state changes.
- * <p>Register your {@code ControllerAlwaysOnStateCallback} implementation with {@link
- * NfcAdapter#registerControllerAlwaysOnStateCallback} and disable it with {@link
- * NfcAdapter#unregisterControllerAlwaysOnStateCallback}.
- * @see #registerControllerAlwaysOnStateCallback
+ * A listener to be invoked when NFC controller always on state changes.
+ * <p>Register your {@code ControllerAlwaysOnListener} implementation with {@link
+ * NfcAdapter#registerControllerAlwaysOnListener} and disable it with {@link
+ * NfcAdapter#unregisterControllerAlwaysOnListener}.
+ * @see #registerControllerAlwaysOnListener
* @hide
*/
@SystemApi
- public interface ControllerAlwaysOnStateCallback {
+ public interface ControllerAlwaysOnListener {
/**
* Called on NFC controller always on state changes
*/
- void onStateChanged(boolean isEnabled);
+ void onControllerAlwaysOnChanged(boolean isEnabled);
}
/**
@@ -748,7 +748,7 @@
mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>();
mTagRemovedListener = null;
mLock = new Object();
- mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService());
+ mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService());
}
/**
@@ -2246,12 +2246,12 @@
* <p>This API is for the NFCC internal state management. It allows to discriminate
* the controller function from the NFC function by keeping the NFC controller on without
* any NFC RF enabled if necessary.
- * <p>This call is asynchronous. Register a callback {@link #ControllerAlwaysOnStateCallback}
- * by {@link #registerControllerAlwaysOnStateCallback} to find out when the operation is
+ * <p>This call is asynchronous. Register a listener {@link #ControllerAlwaysOnListener}
+ * by {@link #registerControllerAlwaysOnListener} to find out when the operation is
* complete.
* <p>If this returns true, then either NFCC always on state has been set based on the value,
- * or a {@link ControllerAlwaysOnStateCallback#onStateChanged(boolean)} will be invoked to
- * indicate the state change.
+ * or a {@link ControllerAlwaysOnListener#onControllerAlwaysOnChanged(boolean)} will be invoked
+ * to indicate the state change.
* If this returns false, then there is some problem that prevents an attempt to turn NFCC
* always on.
* @param value if true the NFCC will be kept on (with no RF enabled if NFC adapter is
@@ -2344,37 +2344,37 @@
}
/**
- * Register a {@link ControllerAlwaysOnStateCallback} to listen for NFC controller always on
+ * Register a {@link ControllerAlwaysOnListener} to listen for NFC controller always on
* state changes
- * <p>The provided callback will be invoked by the given {@link Executor}.
+ * <p>The provided listener will be invoked by the given {@link Executor}.
*
- * @param executor an {@link Executor} to execute given callback
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
- public void registerControllerAlwaysOnStateCallback(
+ public void registerControllerAlwaysOnListener(
@NonNull @CallbackExecutor Executor executor,
- @NonNull ControllerAlwaysOnStateCallback callback) {
- mControllerAlwaysOnStateListener.register(executor, callback);
+ @NonNull ControllerAlwaysOnListener listener) {
+ mControllerAlwaysOnListener.register(executor, listener);
}
/**
- * Unregister the specified {@link ControllerAlwaysOnStateCallback}
- * <p>The same {@link ControllerAlwaysOnStateCallback} object used when calling
- * {@link #registerControllerAlwaysOnStateCallback(Executor, ControllerAlwaysOnStateCallback)}
+ * Unregister the specified {@link ControllerAlwaysOnListener}
+ * <p>The same {@link ControllerAlwaysOnListener} object used when calling
+ * {@link #registerControllerAlwaysOnListener(Executor, ControllerAlwaysOnListener)}
* must be used.
*
- * <p>Callbacks are automatically unregistered when application process goes away
+ * <p>Listeners are automatically unregistered when application process goes away
*
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
- public void unregisterControllerAlwaysOnStateCallback(
- @NonNull ControllerAlwaysOnStateCallback callback) {
- mControllerAlwaysOnStateListener.unregister(callback);
+ public void unregisterControllerAlwaysOnListener(
+ @NonNull ControllerAlwaysOnListener listener) {
+ mControllerAlwaysOnListener.unregister(listener);
}
}
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
new file mode 100644
index 0000000..96707bb
--- /dev/null
+++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java
@@ -0,0 +1,120 @@
+/*
+ * 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.nfc;
+
+import android.annotation.NonNull;
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub {
+ private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName();
+
+ private final INfcAdapter mAdapter;
+
+ private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>();
+
+ private boolean mCurrentState = false;
+ private boolean mIsRegistered = false;
+
+ public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ /**
+ * Register a {@link ControllerAlwaysOnListener} with this
+ * {@link NfcControllerAlwaysOnListener}
+ *
+ * @param executor an {@link Executor} to execute given listener
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void register(@NonNull Executor executor,
+ @NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ if (mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.put(listener, executor);
+ if (!mIsRegistered) {
+ try {
+ mAdapter.registerControllerAlwaysOnListener(this);
+ mIsRegistered = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to register");
+ }
+ }
+ }
+ }
+
+ /**
+ * Unregister the specified {@link ControllerAlwaysOnListener}
+ *
+ * @param listener user implementation of the {@link ControllerAlwaysOnListener}
+ */
+ public void unregister(@NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ if (!mListenerMap.containsKey(listener)) {
+ return;
+ }
+
+ mListenerMap.remove(listener);
+
+ if (mListenerMap.isEmpty() && mIsRegistered) {
+ try {
+ mAdapter.unregisterControllerAlwaysOnListener(this);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to unregister");
+ }
+ mIsRegistered = false;
+ }
+ }
+ }
+
+ private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) {
+ synchronized (this) {
+ Executor executor = mListenerMap.get(listener);
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onControllerAlwaysOnChanged(
+ mCurrentState));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void onControllerAlwaysOnChanged(boolean isEnabled) {
+ synchronized (this) {
+ mCurrentState = isEnabled;
+ for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) {
+ sendCurrentState(cb);
+ }
+ }
+ }
+}
+
diff --git a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
deleted file mode 100644
index 69a9ec7..0000000
--- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.nfc;
-
-import android.annotation.NonNull;
-import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/**
- * @hide
- */
-public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub {
- private static final String TAG = "NfcControllerAlwaysOnStateListener";
-
- private final INfcAdapter mAdapter;
-
- private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>();
-
- private boolean mCurrentState = false;
- private boolean mIsRegistered = false;
-
- public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) {
- mAdapter = adapter;
- }
-
- /**
- * Register a {@link ControllerAlwaysOnStateCallback} with this
- * {@link NfcControllerAlwaysOnStateListener}
- *
- * @param executor an {@link Executor} to execute given callback
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
- */
- public void register(@NonNull Executor executor,
- @NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- if (mCallbackMap.containsKey(callback)) {
- return;
- }
-
- mCallbackMap.put(callback, executor);
- if (!mIsRegistered) {
- try {
- mAdapter.registerControllerAlwaysOnStateCallback(this);
- mIsRegistered = true;
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener");
- }
- }
- }
- }
-
- /**
- * Unregister the specified {@link ControllerAlwaysOnStateCallback}
- *
- * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback}
- */
- public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- if (!mCallbackMap.containsKey(callback)) {
- return;
- }
-
- mCallbackMap.remove(callback);
-
- if (mCallbackMap.isEmpty() && mIsRegistered) {
- try {
- mAdapter.unregisterControllerAlwaysOnStateCallback(this);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener");
- }
- mIsRegistered = false;
- }
- }
- }
-
- private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) {
- synchronized (this) {
- Executor executor = mCallbackMap.get(callback);
-
- final long identity = Binder.clearCallingIdentity();
- try {
- executor.execute(() -> callback.onStateChanged(
- mCurrentState));
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void onControllerAlwaysOnStateChanged(boolean isEnabled) {
- synchronized (this) {
- mCurrentState = isEnabled;
- for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) {
- sendCurrentState(cb);
- }
- }
- }
-}
-
diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING
new file mode 100644
index 0000000..71ad687
--- /dev/null
+++ b/core/java/android/nfc/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "NfcManagerTests"
+ }
+ ]
+}
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index b558edc..ba63ba4 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -36,7 +36,7 @@
* @hide
*/
@IntDef(prefix = {"POWER_COMPONENT_"}, value = {
- POWER_COMPONENT_USAGE,
+ POWER_COMPONENT_SCREEN,
POWER_COMPONENT_CPU,
POWER_COMPONENT_BLUETOOTH,
POWER_COMPONENT_CAMERA,
@@ -49,14 +49,16 @@
POWER_COMPONENT_GNSS,
POWER_COMPONENT_WIFI,
POWER_COMPONENT_WAKELOCK,
- POWER_COMPONENT_SCREEN,
+ POWER_COMPONENT_MEMORY,
+ POWER_COMPONENT_PHONE,
+ POWER_COMPONENT_IDLE,
POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface PowerComponent {
}
- public static final int POWER_COMPONENT_USAGE = 0;
+ public static final int POWER_COMPONENT_SCREEN = 0;
public static final int POWER_COMPONENT_CPU = 1;
public static final int POWER_COMPONENT_BLUETOOTH = 2;
public static final int POWER_COMPONENT_CAMERA = 3;
@@ -69,13 +71,15 @@
public static final int POWER_COMPONENT_GNSS = 10;
public static final int POWER_COMPONENT_WIFI = 11;
public static final int POWER_COMPONENT_WAKELOCK = 12;
- public static final int POWER_COMPONENT_SCREEN = 13;
+ public static final int POWER_COMPONENT_MEMORY = 13;
+ public static final int POWER_COMPONENT_PHONE = 13;
+ public static final int POWER_COMPONENT_IDLE = 15;
// Power that is re-attributed to other battery consumers. For example, for System Server
// this represents the power attributed to apps requesting system services.
// The value should be negative or zero.
- public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 14;
+ public static final int POWER_COMPONENT_REATTRIBUTED_TO_OTHER_CONSUMERS = 16;
- public static final int POWER_COMPONENT_COUNT = 15;
+ public static final int POWER_COMPONENT_COUNT = 17;
public static final int FIRST_CUSTOM_POWER_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_POWER_COMPONENT_ID = 9999;
@@ -87,7 +91,7 @@
* @hide
*/
@IntDef(prefix = {"TIME_COMPONENT_"}, value = {
- TIME_COMPONENT_USAGE,
+ TIME_COMPONENT_SCREEN,
TIME_COMPONENT_CPU,
TIME_COMPONENT_CPU_FOREGROUND,
TIME_COMPONENT_BLUETOOTH,
@@ -98,13 +102,15 @@
TIME_COMPONENT_GNSS,
TIME_COMPONENT_WIFI,
TIME_COMPONENT_WAKELOCK,
- TIME_COMPONENT_SCREEN,
+ TIME_COMPONENT_MEMORY,
+ TIME_COMPONENT_PHONE,
+ TIME_COMPONENT_IDLE,
})
@Retention(RetentionPolicy.SOURCE)
public static @interface TimeComponent {
}
- public static final int TIME_COMPONENT_USAGE = 0;
+ public static final int TIME_COMPONENT_SCREEN = 0;
public static final int TIME_COMPONENT_CPU = 1;
public static final int TIME_COMPONENT_CPU_FOREGROUND = 2;
public static final int TIME_COMPONENT_BLUETOOTH = 3;
@@ -117,9 +123,11 @@
public static final int TIME_COMPONENT_GNSS = 10;
public static final int TIME_COMPONENT_WIFI = 11;
public static final int TIME_COMPONENT_WAKELOCK = 12;
- public static final int TIME_COMPONENT_SCREEN = 13;
+ public static final int TIME_COMPONENT_MEMORY = 13;
+ public static final int TIME_COMPONENT_PHONE = 14;
+ public static final int TIME_COMPONENT_IDLE = 15;
- public static final int TIME_COMPONENT_COUNT = 14;
+ public static final int TIME_COMPONENT_COUNT = 16;
public static final int FIRST_CUSTOM_TIME_COMPONENT_ID = 1000;
public static final int LAST_CUSTOM_TIME_COMPONENT_ID = 9999;
@@ -148,8 +156,7 @@
*/
public static final int POWER_MODEL_MEASURED_ENERGY = 1;
- private final PowerComponents mPowerComponents;
- private String[] mCustomPowerComponentNames;
+ protected final PowerComponents mPowerComponents;
protected BatteryConsumer(@NonNull PowerComponents powerComponents) {
mPowerComponents = powerComponents;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 48f4ca4..8ea59ce 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -24,7 +24,6 @@
import com.android.internal.os.BatteryStatsHistoryIterator;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -233,8 +232,6 @@
mHistoryBuffer = null;
mHistoryTagPool = null;
}
- System.out.println("From Parcel = " + Arrays.toString(
- mCustomPowerComponentNames));
}
@Override
@@ -293,6 +290,7 @@
* Builder for BatteryUsageStats.
*/
public static final class Builder {
+ @NonNull
private final String[] mCustomPowerComponentNames;
private final int mCustomTimeComponentCount;
private final boolean mIncludePowerModels;
@@ -311,11 +309,11 @@
private Parcel mHistoryBuffer;
private List<BatteryStats.HistoryTag> mHistoryTagPool;
- public Builder(String[] customPowerComponentNames, int customTimeComponentCount) {
+ public Builder(@NonNull String[] customPowerComponentNames, int customTimeComponentCount) {
this(customPowerComponentNames, customTimeComponentCount, false);
}
- public Builder(String[] customPowerComponentNames, int customTimeComponentCount,
+ public Builder(@NonNull String[] customPowerComponentNames, int customTimeComponentCount,
boolean includePowerModels) {
mCustomPowerComponentNames = customPowerComponentNames;
mCustomTimeComponentCount = customTimeComponentCount;
diff --git a/core/java/android/os/PowerComponents.java b/core/java/android/os/PowerComponents.java
index 2fea4ac..a0a41f4 100644
--- a/core/java/android/os/PowerComponents.java
+++ b/core/java/android/os/PowerComponents.java
@@ -192,6 +192,19 @@
}
/**
+ * Returns the largest usage duration among all time components.
+ */
+ public long getMaxComponentUsageDurationMillis() {
+ long max = 0;
+ for (int i = mTimeComponentsMs.length - 1; i >= 0; i--) {
+ if (mTimeComponentsMs[i] > max) {
+ max = mTimeComponentsMs[i];
+ }
+ }
+ return max;
+ }
+
+ /**
* Builder for PowerComponents.
*/
static final class Builder {
@@ -312,10 +325,10 @@
}
public void addPowerAndDuration(Builder other) {
- for (int i = 0; i < mPowerComponentsMah.length; i++) {
+ for (int i = mPowerComponentsMah.length - 1; i >= 0; i--) {
mPowerComponentsMah[i] += other.mPowerComponentsMah[i];
}
- for (int i = 0; i < mTimeComponentsMs.length; i++) {
+ for (int i = mTimeComponentsMs.length - 1; i >= 0; i--) {
mTimeComponentsMs[i] += other.mTimeComponentsMs[i];
}
}
diff --git a/core/java/android/os/SystemBatteryConsumer.java b/core/java/android/os/SystemBatteryConsumer.java
index 70b0532..1327978 100644
--- a/core/java/android/os/SystemBatteryConsumer.java
+++ b/core/java/android/os/SystemBatteryConsumer.java
@@ -104,6 +104,13 @@
}
/**
+ * Returns the amount of time this consumer was operating.
+ */
+ public long getUsageDurationMillis() {
+ return mPowerComponents.getMaxComponentUsageDurationMillis();
+ }
+
+ /**
* Writes the contents into a Parcel.
*/
@Override
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 1a40f06..17c90d6 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -886,6 +886,24 @@
}
/**
+ * @param micMuted whether to consider the microphone muted when retrieving audio ops
+ * @return A list of permission groups currently or recently used by all apps by all users in
+ * the current profile group.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS)
+ public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) {
+ // Lazily initialize the usage helper
+ if (mUsageHelper == null) {
+ mUsageHelper = new PermissionUsageHelper(mContext);
+ }
+ return mUsageHelper.getOpUsageData(micMuted);
+ }
+
+ /**
* Determine if a package should be shown in indicators. Only a select few roles, and the
* system app itself, are hidden. These values are updated at most every 15 seconds.
* @hide
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 9bfd75e..51f19eb 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -22,10 +22,10 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.UserHandleAware;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentProvider;
import android.content.ContentResolver;
@@ -129,7 +129,7 @@
public static final int ERROR_STORAGE_FULL = 2;
/**
- * Indicates that the {@link InputStream} passed to {@link #storeCallComposerPictureAsUser}
+ * Indicates that the {@link InputStream} passed to {@link #storeCallComposerPicture}
* was closed.
*
* The caller should retry if this error is encountered, and be sure to not close the stream
@@ -195,9 +195,8 @@
* The caller is responsible for closing the {@link InputStream} after the callback indicating
* success or failure.
*
- * @param context An instance of {@link Context}.
- * @param user The user for whom the picture is stored. If {@code null}, the picture will be
- * stored for all users.
+ * @param context An instance of {@link Context}. The picture will be stored to the user
+ * corresponding to {@link Context#getUser()}.
* @param input An input stream from which the picture to store should be read. The input data
* must be decodeable as either a JPEG, PNG, or GIF image.
* @param executor The {@link Executor} on which to perform the file transfer operation and
@@ -207,12 +206,12 @@
* @hide
*/
@SystemApi
+ @UserHandleAware
@RequiresPermission(allOf = {
Manifest.permission.WRITE_CALL_LOG,
Manifest.permission.INTERACT_ACROSS_USERS
})
- public static void storeCallComposerPictureAsUser(@NonNull Context context,
- @Nullable UserHandle user,
+ public static void storeCallComposerPicture(@NonNull Context context,
@NonNull InputStream input,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<Uri, CallComposerLoggingException> callback) {
@@ -246,12 +245,13 @@
byte[] picData = tmpOut.toByteArray();
UserManager userManager = context.getSystemService(UserManager.class);
+ UserHandle user = context.getUser();
// Nasty casework for the shadow calllog begins...
// First see if we're just inserting for one user. If so, insert into the shadow
// based on whether that user is unlocked.
UserHandle realUser = UserHandle.CURRENT.equals(user)
? android.os.Process.myUserHandle() : user;
- if (realUser != null) {
+ if (realUser != UserHandle.ALL) {
Uri baseUri = userManager.isUserUnlocked(realUser) ? CALL_COMPOSER_PICTURE_URI
: SHADOW_CALL_COMPOSER_PICTURE_URI;
Uri pictureInsertionUri = ContentProvider.maybeAddUserId(baseUri,
@@ -625,7 +625,7 @@
}
/**
- * @param pictureUri {@link Uri} returned from {@link #storeCallComposerPictureAsUser}.
+ * @param pictureUri {@link Uri} returned from {@link #storeCallComposerPicture}.
* Associates that stored picture with this call in the log.
*/
public @NonNull AddCallParametersBuilder setPictureUri(@NonNull Uri pictureUri) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9450994..2616a667 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9136,6 +9136,20 @@
"biometric_debug_enabled";
/**
+ * Whether or not biometric is allowed on Keyguard.
+ * @hide
+ */
+ @Readable
+ public static final String BIOMETRIC_KEYGUARD_ENABLED = "biometric_keyguard_enabled";
+
+ /**
+ * Whether or not biometric is allowed for apps (through BiometricPrompt).
+ * @hide
+ */
+ @Readable
+ public static final String BIOMETRIC_APP_ENABLED = "biometric_app_enabled";
+
+ /**
* Whether the assist gesture should be enabled.
*
* @hide
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 340fa40..4b18c5a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -796,13 +796,14 @@
/**
* Notify {@link PhysicalChannelConfig} has changed for a specific subscription.
*
+ * @param slotIndex for which physical channel configs changed.
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
*/
- public void notifyPhysicalChannelConfigForSubscriber(
- int subId, List<PhysicalChannelConfig> configs) {
+ public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
+ List<PhysicalChannelConfig> configs) {
try {
- sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs);
+ sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs);
} catch (RemoteException ex) {
// system server crash
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 0a3963d..be172f7 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -693,71 +693,79 @@
ThreadedRenderer.setFPSDivisor(divisor);
}
+ private void traceMessage(String msg) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg);
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
- synchronized (mLock) {
- if (!mFrameScheduled) {
- return; // no work to do
- }
-
- if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
- mDebugPrintNextFrameTimeDelta = false;
- Log.d(TAG, "Frame time delta: "
- + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
- }
-
- long intendedFrameTimeNanos = frameTimeNanos;
- startNanos = System.nanoTime();
- final long jitterNanos = startNanos - frameTimeNanos;
- if (jitterNanos >= frameIntervalNanos) {
- final long skippedFrames = jitterNanos / frameIntervalNanos;
- if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
- Log.i(TAG, "Skipped " + skippedFrames + " frames! "
- + "The application may be doing too much work on its main thread.");
- }
- final long lastFrameOffset = jitterNanos % frameIntervalNanos;
- if (DEBUG_JANK) {
- Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
- + "which is more than the frame interval of "
- + (frameIntervalNanos * 0.000001f) + " ms! "
- + "Skipping " + skippedFrames + " frames and setting frame "
- + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
- }
- frameTimeNanos = startNanos - lastFrameOffset;
- }
-
- if (frameTimeNanos < mLastFrameTimeNanos) {
- if (DEBUG_JANK) {
- Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
- + "previously skipped frame. Waiting for next vsync.");
- }
- scheduleVsyncLocked();
- return;
- }
-
- if (mFPSDivisor > 1) {
- long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
- if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
- scheduleVsyncLocked();
- return;
- }
- }
-
- mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
- vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
- mFrameScheduled = false;
- mLastFrameTimeNanos = frameTimeNanos;
- mLastFrameIntervalNanos = frameIntervalNanos;
- mLastVsyncEventData = vsyncEventData;
- }
-
try {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
"Choreographer#doFrame " + vsyncEventData.id);
}
+ synchronized (mLock) {
+ if (!mFrameScheduled) {
+ traceMessage("Frame not scheduled");
+ return; // no work to do
+ }
+
+ if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+ mDebugPrintNextFrameTimeDelta = false;
+ Log.d(TAG, "Frame time delta: "
+ + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+ }
+
+ long intendedFrameTimeNanos = frameTimeNanos;
+ startNanos = System.nanoTime();
+ final long jitterNanos = startNanos - frameTimeNanos;
+ if (jitterNanos >= frameIntervalNanos) {
+ final long skippedFrames = jitterNanos / frameIntervalNanos;
+ if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
+ Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ + "The application may be doing too much work on its main thread.");
+ }
+ final long lastFrameOffset = jitterNanos % frameIntervalNanos;
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ + "which is more than the frame interval of "
+ + (frameIntervalNanos * 0.000001f) + " ms! "
+ + "Skipping " + skippedFrames + " frames and setting frame "
+ + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past.");
+ }
+ frameTimeNanos = startNanos - lastFrameOffset;
+ }
+
+ if (frameTimeNanos < mLastFrameTimeNanos) {
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ + "previously skipped frame. Waiting for next vsync.");
+ }
+ traceMessage("Frame time goes backward");
+ scheduleVsyncLocked();
+ return;
+ }
+
+ if (mFPSDivisor > 1) {
+ long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
+ if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
+ traceMessage("Frame skipped due to FPSDivisor");
+ scheduleVsyncLocked();
+ return;
+ }
+ }
+
+ mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id,
+ vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval);
+ mFrameScheduled = false;
+ mLastFrameTimeNanos = frameTimeNanos;
+ mLastFrameIntervalNanos = frameIntervalNanos;
+ mLastVsyncEventData = vsyncEventData;
+ }
+
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
@@ -870,7 +878,12 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private void scheduleVsyncLocked() {
- mDisplayEventReceiver.scheduleVsync();
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
+ mDisplayEventReceiver.scheduleVsync();
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
private boolean isRunningOnLooperThreadLocked() {
@@ -967,32 +980,40 @@
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
VsyncEventData vsyncEventData) {
- // Post the vsync event to the Handler.
- // The idea is to prevent incoming vsync events from completely starving
- // the message queue. If there are no messages in the queue with timestamps
- // earlier than the frame time, then the vsync event will be processed immediately.
- // Otherwise, messages that predate the vsync event will be handled first.
- long now = System.nanoTime();
- if (timestampNanos > now) {
- Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
- + " ms in the future! Check that graphics HAL is generating vsync "
- + "timestamps using the correct timebase.");
- timestampNanos = now;
- }
+ try {
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW,
+ "Choreographer#onVsync " + vsyncEventData.id);
+ }
+ // Post the vsync event to the Handler.
+ // The idea is to prevent incoming vsync events from completely starving
+ // the message queue. If there are no messages in the queue with timestamps
+ // earlier than the frame time, then the vsync event will be processed immediately.
+ // Otherwise, messages that predate the vsync event will be handled first.
+ long now = System.nanoTime();
+ if (timestampNanos > now) {
+ Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ + " ms in the future! Check that graphics HAL is generating vsync "
+ + "timestamps using the correct timebase.");
+ timestampNanos = now;
+ }
- if (mHavePendingVsync) {
- Log.w(TAG, "Already have a pending vsync event. There should only be "
- + "one at a time.");
- } else {
- mHavePendingVsync = true;
- }
+ if (mHavePendingVsync) {
+ Log.w(TAG, "Already have a pending vsync event. There should only be "
+ + "one at a time.");
+ } else {
+ mHavePendingVsync = true;
+ }
- mTimestampNanos = timestampNanos;
- mFrame = frame;
- mLastVsyncEventData = vsyncEventData;
- Message msg = Message.obtain(mHandler, this);
- msg.setAsynchronous(true);
- mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+ mTimestampNanos = timestampNanos;
+ mFrame = frame;
+ mLastVsyncEventData = vsyncEventData;
+ Message msg = Message.obtain(mHandler, this);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
}
@Override
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index a6d786e..5fcb011 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -185,7 +185,8 @@
}
Log.w(TAG, "close(): capture session still active! Ending now.");
// -> UiThread
- mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ }));
+ final ScrollCaptureCallback callback = mLocal;
+ mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
mActive = false;
}
mActive = false;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e450061..21f75d4 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -104,6 +104,7 @@
private static native void nativeApplyTransaction(long transactionObj, boolean sync);
private static native void nativeMergeTransaction(long transactionObj,
long otherTransactionObj);
+ private static native void nativeClearTransaction(long transactionObj);
private static native void nativeSetAnimationTransaction(long transactionObj);
private static native void nativeSetEarlyWakeupStart(long transactionObj);
private static native void nativeSetEarlyWakeupEnd(long transactionObj);
@@ -2602,6 +2603,19 @@
}
/**
+ * Clear the transaction object, without applying it.
+ *
+ * @hide
+ */
+ public void clear() {
+ mResizedSurfaces.clear();
+ mReparentedSurfaces.clear();
+ if (mNativeObject != 0) {
+ nativeClearTransaction(mNativeObject);
+ }
+ }
+
+ /**
* Release the native transaction object, without applying it.
*/
@Override
@@ -3411,10 +3425,14 @@
public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
if (mNativeObject == 0) {
dest.writeInt(0);
- } else {
- dest.writeInt(1);
+ return;
}
+
+ dest.writeInt(1);
nativeWriteTransactionToParcel(mNativeObject, dest);
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ nativeClearTransaction(mNativeObject);
+ }
}
private void readFromParcel(Parcel in) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 2b96a14..7bdf5cf 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -30,6 +30,7 @@
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.HardwareRenderer;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
@@ -221,8 +222,35 @@
private int mPendingReportDraws;
- private SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
- private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+ /**
+ * Transaction that should be used from the render thread. This transaction is only thread safe
+ * with other calls directly from the render thread.
+ */
+ private final SurfaceControl.Transaction mRtTransaction = new SurfaceControl.Transaction();
+
+ /**
+ * Transaction that should be used whe
+ * {@link HardwareRenderer.FrameDrawingCallback#onFrameDraw} is invoked. All
+ * frame callbacks can use the same transaction since they will be thread safe
+ */
+ private final SurfaceControl.Transaction mFrameCallbackTransaction =
+ new SurfaceControl.Transaction();
+
+ /**
+ * Transaction that should be used for
+ * {@link RenderNode.PositionUpdateListener#positionChanged(long, int, int, int, int)}
+ * The callback is invoked from a thread pool so it's not thread safe with other render thread
+ * transactions. Keep the transactions for position changed callbacks on its own transaction.
+ */
+ private final SurfaceControl.Transaction mPositionChangedTransaction =
+ new SurfaceControl.Transaction();
+
+ /**
+ * A temporary transaction holder that should only be used when applying right away. There
+ * should be no assumption about thread safety for this transaction.
+ */
+ private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
private int mParentSurfaceSequenceId;
private RemoteAccessibilityController mRemoteAccessibilityController =
@@ -432,7 +460,6 @@
* This gets called on a RenderThread worker thread, so members accessed here must
* be protected by a lock.
*/
- final boolean useBLAST = useBLASTSync(viewRoot);
viewRoot.registerRtFrameCallback(frame -> {
try {
synchronized (mSurfaceControlLock) {
@@ -456,8 +483,9 @@
Log.d(TAG, System.identityHashCode(this)
+ " updateSurfaceAlpha RT: set alpha=" + alpha);
}
- mRtTransaction.setAlpha(mSurfaceControl, alpha);
- applyRtTransaction(frame);
+
+ mFrameCallbackTransaction.setAlpha(mSurfaceControl, alpha);
+ applyOrMergeTransaction(mFrameCallbackTransaction, frame);
}
// It's possible that mSurfaceControl is released in the UI thread before
// the transaction completes. If that happens, an exception is thrown, which
@@ -806,7 +834,6 @@
* This gets called on a RenderThread worker thread, so members accessed here must
* be protected by a lock.
*/
- final boolean useBLAST = useBLASTSync(viewRoot);
viewRoot.registerRtFrameCallback(frame -> {
try {
synchronized (mSurfaceControlLock) {
@@ -814,8 +841,8 @@
return;
}
- updateRelativeZ(mRtTransaction);
- applyRtTransaction(frame);
+ updateRelativeZ(mFrameCallbackTransaction);
+ applyOrMergeTransaction(mFrameCallbackTransaction, frame);
}
// It's possible that mSurfaceControl is released in the UI thread before
// the transaction completes. If that happens, an exception is thrown, which
@@ -1380,22 +1407,21 @@
return mRTLastReportedPosition;
}
- private void setParentSpaceRectangle(Rect position, long frameNumber) {
+ private void setParentSpaceRectangle(Rect position, long frameNumber, Transaction t) {
final ViewRootImpl viewRoot = getViewRootImpl();
- applySurfaceTransforms(mSurfaceControl, mRtTransaction, position);
- applyChildSurfaceTransaction_renderWorker(mRtTransaction, viewRoot.mSurface, frameNumber);
- applyRtTransaction(frameNumber);
+ applySurfaceTransforms(mSurfaceControl, t, position);
+ applyChildSurfaceTransaction_renderWorker(t, viewRoot.mSurface, frameNumber);
+ applyOrMergeTransaction(t, frameNumber);
}
- private void applyRtTransaction(long frameNumber) {
+ private void applyOrMergeTransaction(Transaction t, long frameNumber) {
final ViewRootImpl viewRoot = getViewRootImpl();
boolean useBLAST = viewRoot != null && useBLASTSync(viewRoot);
if (useBLAST) {
// If we are using BLAST, merge the transaction with the viewroot buffer transaction.
- viewRoot.mergeWithNextTransaction(mRtTransaction, frameNumber);
- return;
+ viewRoot.mergeWithNextTransaction(t, frameNumber);
} else {
- mRtTransaction.apply();
+ t.apply();
}
}
@@ -1436,7 +1462,8 @@
left, top, right, bottom));
}
mRTLastReportedPosition.set(left, top, right, bottom);
- setParentSpaceRectangle(mRTLastReportedPosition, frameNumber);
+ setParentSpaceRectangle(mRTLastReportedPosition, frameNumber,
+ mPositionChangedTransaction);
// Now overwrite mRTLastReportedPosition with our values
} catch (Exception ex) {
Log.e(TAG, "Exception from repositionChild", ex);
@@ -1448,7 +1475,7 @@
float bottom, float vecX, float vecY, float maxStretch) {
mRtTransaction.setStretchEffect(mSurfaceControl, left, top, right, bottom, vecX, vecY,
maxStretch);
- applyRtTransaction(frameNumber);
+ applyOrMergeTransaction(mRtTransaction, frameNumber);
}
@Override
@@ -1468,14 +1495,12 @@
* need to hold the lock here.
*/
synchronized (mSurfaceControlLock) {
- final ViewRootImpl viewRoot = getViewRootImpl();
-
mRtTransaction.hide(mSurfaceControl);
if (mRtReleaseSurfaces) {
mRtReleaseSurfaces = false;
releaseSurfaces(mRtTransaction);
}
- applyRtTransaction(frameNumber);
+ applyOrMergeTransaction(mRtTransaction, frameNumber);
mRtHandlingPositionUpdates = false;
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e8a4bb7..76eb882 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3906,7 +3906,10 @@
mDrawsNeededToReport = 0;
mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
} catch (RemoteException e) {
- // Have fun!
+ Log.e(mTag, "Unable to report draw finished", e);
+ mSurfaceChangedTransaction.apply();
+ } finally {
+ mSurfaceChangedTransaction.clear();
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6edd071..616910a 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2230,9 +2230,7 @@
public void removeImeSurface(IBinder windowToken) {
synchronized (mH) {
try {
- final Completable.Void value = Completable.createVoid();
- mService.removeImeSurfaceFromWindow(windowToken, ResultCallbacks.of(value));
- Completable.getResult(value);
+ mService.removeImeSurfaceFromWindowAsync(windowToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index f4fdf35..c09e8bd 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -302,6 +302,11 @@
}
final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
translatedResult.valueAt(i);
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.v(TAG, "onVirtualViewTranslationCompleted: receive "
+ + virtualChildResponse + " for AutofillId " + autofillId);
+ }
mActivity.runOnUiThread(() -> {
if (view.getViewTranslationCallback() == null) {
if (DEBUG) {
@@ -341,8 +346,13 @@
}
for (int i = 0; i < resultCount; i++) {
final ViewTranslationResponse response = translatedResult.valueAt(i);
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.v(TAG, "onTranslationCompleted: response= " + response);
+ }
final AutofillId autofillId = response.getAutofillId();
if (autofillId == null) {
+ Log.w(TAG, "No AutofillId is set in ViewTranslationResponse:" + response);
continue;
}
final View view = mViews.get(autofillId).get();
@@ -394,6 +404,9 @@
final TranslationRequest request = new TranslationRequest.Builder()
.setViewTranslationRequests(requests)
.build();
+ if (DEBUG) {
+ Log.d(TAG, "sendTranslationRequest: request= " + request);
+ }
translator.requestUiTranslate(request, (r) -> r.run(), this::onTranslationCompleted);
}
@@ -508,10 +521,17 @@
private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
synchronized (mLock) {
final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
+ if (views.size() == 0) {
+ Log.w(TAG, "No views can be excuted for runForEachView.");
+ }
mActivity.runOnUiThread(() -> {
final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
final View view = views.valueAt(i).get();
+ if (DEBUG) {
+ // TODO(b/182433547): remove before S release
+ Log.d(TAG, "runForEachView: view= " + view);
+ }
if (view == null || view.getViewTranslationCallback() == null) {
if (DEBUG) {
Log.d(TAG, "View was gone or ViewTranslationCallback for autofillid "
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index cd91d02..95a3dc7 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -104,25 +104,31 @@
/**
* The velocity threshold before the spring animation is considered settled.
- * The idea here is that velocity should be less than 1 pixel per frame (~16ms).
+ * The idea here is that velocity should be less than 0.1 pixel per second.
*/
- private static final double VELOCITY_THRESHOLD = 1.0 / 0.016;
+ private static final double VELOCITY_THRESHOLD = 0.1;
/**
* The value threshold before the spring animation is considered close enough to
- * the destination to be settled. This should be around 1 pixel.
+ * the destination to be settled. This should be around 0.01 pixel.
*/
- private static final double VALUE_THRESHOLD = 1;
+ private static final double VALUE_THRESHOLD = 0.01;
/**
* The natural frequency of the stretch spring.
*/
- private static final double NATURAL_FREQUENCY = 17.55;
+ private static final double NATURAL_FREQUENCY = 24.657;
/**
* The damping ratio of the stretch spring.
*/
- private static final double DAMPING_RATIO = 0.92;
+ private static final double DAMPING_RATIO = 0.98;
+
+ /**
+ * The variation of the velocity for the stretch effect when it meets the bound.
+ * if value is > 1, it will accentuate the absorption of the movement.
+ */
+ private static final float ON_ABSORB_VELOCITY_ADJUSTMENT = 13f;
/** @hide */
@IntDef({TYPE_GLOW, TYPE_STRETCH})
@@ -460,7 +466,7 @@
public void onAbsorb(int velocity) {
if (mEdgeEffectType == TYPE_STRETCH) {
mState = STATE_RECEDE;
- mVelocity = velocity;
+ mVelocity = velocity * ON_ABSORB_VELOCITY_ADJUSTMENT;
mDistance = 0;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
} else {
@@ -782,8 +788,8 @@
* considered at rest or false if it is still animating.
*/
private boolean isAtEquilibrium() {
- double displacement = mDistance * mHeight * LINEAR_STRETCH_INTENSITY; // in pixels
- double velocity = mVelocity * LINEAR_STRETCH_INTENSITY;
+ double displacement = mDistance * mHeight; // in pixels
+ double velocity = mVelocity;
return Math.abs(velocity) < VELOCITY_THRESHOLD
&& Math.abs(displacement) < VALUE_THRESHOLD;
}
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 296d93c..a479b8a 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.NonNull;
+import android.os.Build;
import android.text.method.TranslationTransformationMethod;
import android.util.Log;
import android.view.View;
@@ -34,7 +35,10 @@
private static final String TAG = "TextViewTranslationCallback";
- private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
+ // TODO(b/182433547): remove Build.IS_DEBUGGABLE before ship. Enable the logging in debug build
+ // to help the debug during the development phase
+ private static final boolean DEBUG = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)
+ || Build.IS_DEBUGGABLE;
private TranslationTransformationMethod mTranslationTransformation;
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index 88584f4..d84f571 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -105,6 +105,7 @@
* a {@link com.android.server.wm.DisplayArea} by
* {@link #attachToDisplayArea(int, int, Bundle)}.
*
+ * @see WindowProviderService#attachToWindowToken(IBinder))
* @see IWindowManager#attachWindowContextToWindowToken(IBinder, IBinder)
*/
public void attachToWindowToken(IBinder windowToken) {
diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java
new file mode 100644
index 0000000..b8619fb
--- /dev/null
+++ b/core/java/android/window/WindowProviderService.java
@@ -0,0 +1,138 @@
+/*
+ * 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 android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
+import android.annotation.UiContext;
+import android.app.ActivityThread;
+import android.app.LoadedApk;
+import android.app.Service;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.view.Display;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.WindowType;
+import android.view.WindowManagerImpl;
+
+// TODO(b/159767464): handle #onConfigurationChanged(Configuration)
+/**
+ * A {@link Service} responsible for showing a non-activity window, such as software keyboards or
+ * accessibility overlay windows. This {@link Service} has similar behavior to
+ * {@link WindowContext}, but is represented as {@link Service}.
+ *
+ * @see android.inputmethodservice.InputMethodService
+ * @see android.accessibilityservice.AccessibilityService
+ *
+ * @hide
+ */
+@TestApi
+@UiContext
+public abstract class WindowProviderService extends Service {
+
+ private final WindowTokenClient mWindowToken = new WindowTokenClient();
+ private final WindowContextController mController = new WindowContextController(mWindowToken);
+ private WindowManager mWindowManager;
+
+ /**
+ * Returns the type of this {@link WindowProviderService}.
+ * Each inheriting class must implement this method to provide the type of the window. It is
+ * used similar to {@code type} of {@link Context#createWindowContext(int, Bundle)}
+ *
+ * @see Context#createWindowContext(int, Bundle)
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("OnNameExpected")
+ // Suppress the lint because it is not a callback and users should provide window type
+ // so we cannot make it final.
+ public abstract @WindowType int getWindowType();
+
+ /**
+ * Returns the option of this {@link WindowProviderService}.
+ * Default is {@code null}. The inheriting class can implement this method to provide the
+ * customization {@code option} of the window. It is used similar to {@code options} of
+ * {@link Context#createWindowContext(int, Bundle)}
+ *
+ * @see Context#createWindowContext(int, Bundle)
+ *
+ * @hide
+ */
+ @TestApi
+ @SuppressLint({"OnNameExpected", "NullableCollection"})
+ // Suppress the lint because it is not a callback and users may override this API to provide
+ // launch option. Also, the return value of this API is null by default.
+ @Nullable
+ public Bundle getWindowContextOptions() {
+ return null;
+ }
+
+ /**
+ * Attaches this WindowProviderService to the {@code windowToken}.
+ *
+ * @hide
+ */
+ @TestApi
+ public final void attachToWindowToken(@NonNull IBinder windowToken) {
+ mController.attachToWindowToken(windowToken);
+ }
+
+ /** @hide */
+ @Override
+ public final Context createServiceBaseContext(ActivityThread mainThread,
+ LoadedApk packageInfo) {
+ final Context context = super.createServiceBaseContext(mainThread, packageInfo);
+ // Always associate with the default display at initialization.
+ final Display defaultDisplay = context.getSystemService(DisplayManager.class)
+ .getDisplay(DEFAULT_DISPLAY);
+ return context.createTokenContext(mWindowToken, defaultDisplay);
+ }
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mWindowToken.attachContext(this);
+ mController.attachToDisplayArea(getWindowType(), getDisplayId(), getWindowContextOptions());
+ mWindowManager = WindowManagerImpl.createWindowContextWindowManager(this);
+ }
+
+ @SuppressLint("OnNameExpected")
+ @Override
+ // Suppress the lint because ths is overridden from Context.
+ public @Nullable Object getSystemService(@NonNull String name) {
+ if (WINDOW_SERVICE.equals(name)) {
+ return mWindowManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ @CallSuper
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mController.detachIfNeeded();
+ }
+}
diff --git a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
index 2f49582..aa416c5 100644
--- a/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
+++ b/core/java/com/android/internal/os/AmbientDisplayPowerCalculator.java
@@ -52,8 +52,8 @@
measuredEnergyUC, mPowerEstimator, durationMs);
builder.getOrCreateSystemBatteryConsumerBuilder(
SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah, powerModel)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs);
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN, powerMah, powerModel)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN, durationMs);
}
/**
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index e83f365..cb1900f 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6951,9 +6951,9 @@
/**
* Returns the names of custom power components.
*/
- public @Nullable String[] getCustomPowerComponentNames() {
+ public @NonNull String[] getCustomPowerComponentNames() {
if (mGlobalMeasuredEnergyStats == null) {
- return null;
+ return new String[0];
}
final String[] names = mGlobalMeasuredEnergyStats.getCustomBucketNames();
for (int i = 0; i < names.length; i++) {
diff --git a/core/java/com/android/internal/os/BinderLatencyObserver.java b/core/java/com/android/internal/os/BinderLatencyObserver.java
index 59cc66d..4ca59be 100644
--- a/core/java/com/android/internal/os/BinderLatencyObserver.java
+++ b/core/java/com/android/internal/os/BinderLatencyObserver.java
@@ -16,22 +16,37 @@
package com.android.internal.os;
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
import android.annotation.Nullable;
import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BinderInternal.CallSession;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
+import com.android.internal.util.FrameworkStatsLog;
import java.util.Random;
/** Collects statistics about Binder call latency per calling API and method. */
public class BinderLatencyObserver {
private static final String TAG = "BinderLatencyObserver";
+ private static final int MAX_ATOM_SIZE_BYTES = 4064;
+ // Be conservative and leave 1K space for the last histogram so we don't go over the size limit.
+ private static final int LAST_HISTOGRAM_BUFFER_SIZE_BYTES = 1000;
+
+ // Latency observer parameters.
public static final int PERIODIC_SAMPLING_INTERVAL_DEFAULT = 10;
+ public static final int STATSD_PUSH_INTERVAL_MINUTES_DEFAULT = 360;
// Histogram buckets parameters.
public static final int BUCKET_COUNT_DEFAULT = 100;
@@ -50,20 +65,124 @@
private int mFirstBucketSize = FIRST_BUCKET_SIZE_DEFAULT;
private float mBucketScaleFactor = BUCKET_SCALE_FACTOR_DEFAULT;
+ private int mStatsdPushIntervalMinutes = STATSD_PUSH_INTERVAL_MINUTES_DEFAULT;
+
private final Random mRandom;
private BinderLatencyBuckets mLatencyBuckets;
+ private final Handler mLatencyObserverHandler;
+
+ private Runnable mLatencyObserverRunnable = new Runnable() {
+ @Override
+ public void run() {
+ // Schedule the next push.
+ noteLatencyDelayed();
+
+ ArrayMap<LatencyDims, int[]> histogramMap;
+ synchronized (mLock) {
+ // Copy the histograms map so we don't use the lock for longer than needed.
+ histogramMap = new ArrayMap<>(mLatencyHistograms);
+ mLatencyHistograms.clear();
+ }
+
+ BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+ ProtoOutputStream proto = new ProtoOutputStream();
+ int histogramsWritten = 0;
+
+ for (LatencyDims dims : histogramMap.keySet()) {
+ // Start a new atom if the next histogram risks going over the atom size limit.
+ if (proto.getRawSize() + LAST_HISTOGRAM_BUFFER_SIZE_BYTES > getMaxAtomSizeBytes()) {
+ if (histogramsWritten > 0) {
+ writeAtomToStatsd(proto);
+ }
+ proto = new ProtoOutputStream();
+ histogramsWritten = 0;
+ }
+
+ String transactionName = resolver.getMethodName(
+ dims.getBinderClass(), dims.getTransactionCode());
+ fillApiStatsProto(proto, dims, transactionName, histogramMap.get(dims));
+ histogramsWritten++;
+ }
+ // Push the final atom.
+ if (histogramsWritten > 0) {
+ writeAtomToStatsd(proto);
+ }
+ }
+ };
+
+ private void fillApiStatsProto(
+ ProtoOutputStream proto, LatencyDims dims, String transactionName, int[] histogram) {
+ // Find the part of the histogram to write.
+ int firstNonEmptyBucket = 0;
+ for (int i = 0; i < mBucketCount; i++) {
+ if (histogram[i] != 0) {
+ firstNonEmptyBucket = i;
+ break;
+ }
+ }
+ int lastNonEmptyBucket = mBucketCount - 1;
+ for (int i = mBucketCount - 1; i >= 0; i--) {
+ if (histogram[i] != 0) {
+ lastNonEmptyBucket = i;
+ break;
+ }
+ }
+
+ // Start a new ApiStats proto.
+ long apiStatsToken = proto.start(RepeatedApiStats.API_STATS);
+
+ // Write the dims.
+ long dimsToken = proto.start(ApiStats.DIMS);
+ proto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+ proto.write(Dims.SERVICE_CLASS_NAME, dims.getBinderClass().getName());
+ proto.write(Dims.SERVICE_METHOD_NAME, transactionName);
+ proto.end(dimsToken);
+
+ // Write the histogram.
+ proto.write(ApiStats.FIRST_BUCKET_INDEX, firstNonEmptyBucket);
+ for (int i = firstNonEmptyBucket; i <= lastNonEmptyBucket; i++) {
+ proto.write(ApiStats.BUCKETS, histogram[i]);
+ }
+
+ proto.end(apiStatsToken);
+ }
+
+ protected int getMaxAtomSizeBytes() {
+ return MAX_ATOM_SIZE_BYTES;
+ }
+
+ protected void writeAtomToStatsd(ProtoOutputStream atom) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.BINDER_LATENCY_REPORTED,
+ atom.getBytes(),
+ mPeriodicSamplingInterval,
+ 1);
+ }
+
+ private void noteLatencyDelayed() {
+ mLatencyObserverHandler.removeCallbacks(mLatencyObserverRunnable);
+ mLatencyObserverHandler.postDelayed(mLatencyObserverRunnable,
+ mStatsdPushIntervalMinutes * 60 * 1000);
+ }
+
/** Injector for {@link BinderLatencyObserver}. */
public static class Injector {
public Random getRandomGenerator() {
return new Random();
}
+
+ public Handler getHandler() {
+ return new Handler(Looper.getMainLooper());
+ }
}
public BinderLatencyObserver(Injector injector) {
mRandom = injector.getRandomGenerator();
+ mLatencyObserverHandler = injector.getHandler();
mLatencyBuckets = new BinderLatencyBuckets(
mBucketCount, mFirstBucketSize, mBucketScaleFactor);
+ noteLatencyDelayed();
}
/** Should be called when a Binder call completes, will store latency data. */
@@ -73,7 +192,8 @@
}
LatencyDims dims = new LatencyDims(s.binderClass, s.transactionCode);
- long callDuration = getElapsedRealtimeMicro() - s.timeStarted;
+ long elapsedTimeMicro = getElapsedRealtimeMicro();
+ long callDuration = elapsedTimeMicro - s.timeStarted;
// Find the bucket this sample should go to.
int bucketIdx = mLatencyBuckets.sampleToBucket(
@@ -117,6 +237,22 @@
}
}
+ /** Updates the statsd push interval. */
+ public void setPushInterval(int pushIntervalMinutes) {
+ if (pushIntervalMinutes <= 0) {
+ Slog.w(TAG, "Ignored invalid push interval (value must be positive): "
+ + pushIntervalMinutes);
+ return;
+ }
+
+ synchronized (mLock) {
+ if (pushIntervalMinutes != mStatsdPushIntervalMinutes) {
+ mStatsdPushIntervalMinutes = pushIntervalMinutes;
+ reset();
+ }
+ }
+ }
+
/** Updates the histogram buckets parameters. */
public void setHistogramBucketsParams(
int bucketCount, int firstBucketSize, float bucketScaleFactor) {
@@ -138,6 +274,7 @@
synchronized (mLock) {
mLatencyHistograms.clear();
}
+ noteLatencyDelayed();
}
/** Container for binder latency information. */
@@ -187,4 +324,9 @@
public ArrayMap<LatencyDims, int[]> getLatencyHistograms() {
return mLatencyHistograms;
}
+
+ @VisibleForTesting
+ public Runnable getStatsdPushRunnable() {
+ return mLatencyObserverRunnable;
+ }
}
diff --git a/core/java/com/android/internal/os/IdlePowerCalculator.java b/core/java/com/android/internal/os/IdlePowerCalculator.java
index 4a4991b..5cb54bd 100644
--- a/core/java/com/android/internal/os/IdlePowerCalculator.java
+++ b/core/java/com/android/internal/os/IdlePowerCalculator.java
@@ -54,8 +54,8 @@
BatteryStats.STATS_SINCE_CHARGED);
if (mPowerMah != 0) {
builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_IDLE)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, mPowerMah)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, mDurationMs);
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE, mPowerMah)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_IDLE, mDurationMs);
}
}
diff --git a/core/java/com/android/internal/os/MemoryPowerCalculator.java b/core/java/com/android/internal/os/MemoryPowerCalculator.java
index 21dcce9..9ec40c6 100644
--- a/core/java/com/android/internal/os/MemoryPowerCalculator.java
+++ b/core/java/com/android/internal/os/MemoryPowerCalculator.java
@@ -32,8 +32,8 @@
final double powerMah = calculatePower(batteryStats, rawRealtimeUs,
BatteryStats.STATS_SINCE_CHARGED);
builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_MEMORY)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, durationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, powerMah);
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MEMORY, durationMs)
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY, powerMah);
}
@Override
diff --git a/core/java/com/android/internal/os/PhonePowerCalculator.java b/core/java/com/android/internal/os/PhonePowerCalculator.java
index 362ca07..6f279d99 100644
--- a/core/java/com/android/internal/os/PhonePowerCalculator.java
+++ b/core/java/com/android/internal/os/PhonePowerCalculator.java
@@ -45,8 +45,8 @@
final double phoneOnPower = mPowerEstimator.calculatePower(phoneOnTimeMs);
if (phoneOnPower != 0) {
builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_PHONE)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE, phoneOnPower)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE, phoneOnTimeMs);
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_PHONE, phoneOnPower)
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_PHONE, phoneOnTimeMs);
}
}
diff --git a/core/java/com/android/internal/os/ScreenPowerCalculator.java b/core/java/com/android/internal/os/ScreenPowerCalculator.java
index 0267def..0743c89 100644
--- a/core/java/com/android/internal/os/ScreenPowerCalculator.java
+++ b/core/java/com/android/internal/os/ScreenPowerCalculator.java
@@ -96,9 +96,9 @@
}
builder.getOrCreateSystemBatteryConsumerBuilder(SystemBatteryConsumer.DRAIN_TYPE_SCREEN)
- .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE,
+ .setUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN,
totalPowerAndDuration.durationMs)
- .setConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE,
+ .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
Math.max(totalPowerAndDuration.powerMah, totalAppPower), powerModel)
.setPowerConsumedByApps(totalAppPower);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index e438d39..a0a0f32 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -92,7 +92,7 @@
void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
- void notifyPhysicalChannelConfigForSubscriber(in int subId,
+ void notifyPhysicalChannelConfigForSubscriber(in int phoneId, in int subId,
in List<PhysicalChannelConfig> configs);
void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason);
void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
index 4613dad..d5c0f60 100644
--- a/core/java/com/android/internal/util/State.java
+++ b/core/java/com/android/internal/util/State.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.SuppressLint;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Message;
@@ -25,6 +26,7 @@
*
* The class for implementing states in a StateMachine
*/
+@SuppressLint("AndroidFrameworkRequiresPermission")
public class State implements IState {
/**
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 50bbfc5..fd13c26b 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -89,8 +89,7 @@
/** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */
oneway void removeImeSurface(in IVoidResultCallback resultCallback);
/** Remove the IME surface. Requires passing the currently focused window. */
- oneway void removeImeSurfaceFromWindow(in IBinder windowToken,
- in IVoidResultCallback resultCallback);
+ oneway void removeImeSurfaceFromWindowAsync(in IBinder windowToken);
oneway void startProtoDump(in byte[] protoDump, int source, String where,
in IVoidResultCallback resultCallback);
oneway void isImeTraceEnabled(in IBooleanResultCallback resultCallback);
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
index a41511b..8aa2d57 100644
--- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.annotation.UiThread;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.HardwareRenderer;
@@ -26,6 +27,7 @@
import android.graphics.RectF;
import android.graphics.RenderNode;
import android.os.CancellationSignal;
+import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display.ColorMode;
@@ -53,11 +55,14 @@
private static final String TAG = "ScrollCaptureViewSupport";
- private static final boolean WAIT_FOR_ANIMATION = true;
+ private static final String SETTING_CAPTURE_DELAY = "screenshot.scroll_capture_delay";
+ private static final long SETTING_CAPTURE_DELAY_DEFAULT = 60L; // millis
private final WeakReference<V> mWeakView;
private final ScrollCaptureViewHelper<V> mViewHelper;
private final ViewRenderer mRenderer;
+ private final long mPostScrollDelayMillis;
+
private boolean mStarted;
private boolean mEnded;
@@ -66,6 +71,10 @@
mRenderer = new ViewRenderer();
// TODO(b/177649144): provide access to color space from android.media.Image
mViewHelper = viewHelper;
+ Context context = containingView.getContext();
+ ContentResolver contentResolver = context.getContentResolver();
+ mPostScrollDelayMillis = Settings.Global.getLong(contentResolver,
+ SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT);
}
/** Based on ViewRootImpl#updateColorModeIfNeeded */
@@ -120,37 +129,41 @@
public final void onScrollCaptureImageRequest(ScrollCaptureSession session,
CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) {
if (signal.isCanceled()) {
+ Log.w(TAG, "onScrollCaptureImageRequest: cancelled!");
return;
}
+
V view = mWeakView.get();
if (view == null || !view.isVisibleToUser()) {
// Signal to the controller that we have a problem and can't continue.
onComplete.accept(new Rect());
return;
}
+
// Ask the view to scroll as needed to bring this area into view.
ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
requestRect);
+
if (scrollResult.availableArea.isEmpty()) {
onComplete.accept(scrollResult.availableArea);
return;
}
- view.invalidate(); // don't wait for vsync
// For image capture, shift back by scrollDelta to arrive at the location within the view
// where the requested content will be drawn
Rect viewCaptureArea = new Rect(scrollResult.availableArea);
viewCaptureArea.offset(0, -scrollResult.scrollDelta);
- if (WAIT_FOR_ANIMATION) {
- view.postOnAnimation(() -> {
+ Runnable captureAction = () -> {
+ if (signal.isCanceled()) {
+ Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render.");
+ } else {
mRenderer.renderView(view, viewCaptureArea);
onComplete.accept(new Rect(scrollResult.availableArea));
- });
- } else {
- mRenderer.renderView(view, viewCaptureArea);
- onComplete.accept(new Rect(scrollResult.availableArea));
- }
+ }
+ };
+
+ view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis);
}
@Override
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index 7a8ead6..de3b6a4 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -165,15 +165,23 @@
private void updateColors() {
if (shouldShowNumber() && !mDisallowColor) {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+ if (mHighlightPillColor != 0) {
+ mPillView.setBackgroundTintList(ColorStateList.valueOf(mHighlightPillColor));
+ }
mPillView.setBackgroundTintMode(PorterDuff.Mode.SRC_IN);
mIconView.setColorFilter(mHighlightTextColor, PorterDuff.Mode.SRC_IN);
- mNumberView.setTextColor(mHighlightTextColor);
+ if (mHighlightTextColor != 0) {
+ mNumberView.setTextColor(mHighlightTextColor);
+ }
} else {
- mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+ if (mDefaultPillColor != 0) {
+ mPillView.setBackgroundTintList(ColorStateList.valueOf(mDefaultPillColor));
+ }
mPillView.setBackgroundTintMode(PorterDuff.Mode.SRC_IN);
mIconView.setColorFilter(mDefaultTextColor, PorterDuff.Mode.SRC_IN);
- mNumberView.setTextColor(mDefaultTextColor);
+ if (mDefaultTextColor != 0) {
+ mNumberView.setTextColor(mDefaultTextColor);
+ }
}
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b342755..ffba628 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1562,6 +1562,13 @@
reinterpret_cast<SurfaceComposerClient::Transaction *>(nativeObject);
if (self != nullptr) {
self->writeToParcel(parcel);
+ }
+}
+
+static void nativeClearTransaction(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ SurfaceComposerClient::Transaction* const self =
+ reinterpret_cast<SurfaceComposerClient::Transaction*>(nativeObject);
+ if (self != nullptr) {
self->clear();
}
}
@@ -1880,6 +1887,8 @@
(void*)nativeReadTransactionFromParcel },
{"nativeWriteTransactionToParcel", "(JLandroid/os/Parcel;)V",
(void*)nativeWriteTransactionToParcel },
+ {"nativeClearTransaction", "(J)V",
+ (void*)nativeClearTransaction },
{"nativeMirrorSurface", "(J)J",
(void*)nativeMirrorSurface },
{"nativeSetGlobalShadowSettings", "([F[FFFF)V",
diff --git a/core/proto/android/internal/binder_latency.proto b/core/proto/android/internal/binder_latency.proto
new file mode 100644
index 0000000..e32c3e3
--- /dev/null
+++ b/core/proto/android/internal/binder_latency.proto
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package com.android.internal.os;
+
+option java_outer_classname = "BinderLatencyProto";
+
+/**
+ * RepeatedApiStats proto from atoms.proto, duplicated here so that it's
+ * accessible in the build.
+ * Must be kept in sync with the version in atoms.proto.
+ */
+
+message RepeatedApiStats {
+ repeated ApiStats api_stats = 1;
+}
+
+message Dims {
+ enum ProcessSource {
+ UNKNOWN_PROCESS_SOURCE = 0;
+ SYSTEM_SERVER = 1;
+ TELEPHONY = 2;
+ }
+
+ enum ServiceClassName {
+ UNKNOWN_CLASS = 0;
+ }
+ enum ServiceMethodName {
+ UNKNOWN_METHOD = 0;
+ }
+
+ // Required.
+ optional ProcessSource process_source = 1;
+
+ // The class name of the API making the call to Binder. Enum value
+ // is preferred as uses much less data to store.
+ // This field does not contain PII.
+ oneof service_class {
+ ServiceClassName service_class_name_as_enum = 2;
+ string service_class_name = 3;
+ }
+
+ // Method name of the API call. It can also be a transaction code if we
+ // cannot resolve it to a name. See Binder#getTransactionName. Enum value
+ // is preferred as uses much less data to store.
+ // This field does not contain PII.
+ oneof service_method {
+ ServiceMethodName service_method_name_as_enum = 4;
+ string service_method_name = 5;
+ }
+}
+
+message ApiStats {
+ // required.
+ optional Dims dims = 1;
+
+ // Indicates the first bucket that had any data. Allows omitting any empty
+ // buckets at the start of the bucket list and thus save on data size.
+ optional int32 first_bucket_index = 2;
+ // Stores the count of samples for each bucket. The number of buckets and
+ // their sizes are controlled server side with a flag.
+ repeated int32 buckets = 3;
+}
\ No newline at end of file
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index a7127ad..b157146 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -308,6 +308,7 @@
optional float minimize_amount = 27;
optional bool created_by_organizer = 28;
optional string affinity = 29;
+ optional bool has_child_pip_activity = 30;
}
/* represents ActivityRecordProto */
diff --git a/core/res/res/layout/notification_expand_button.xml b/core/res/res/layout/notification_expand_button.xml
index b969fa4..e752431 100644
--- a/core/res/res/layout/notification_expand_button.xml
+++ b/core/res/res/layout/notification_expand_button.xml
@@ -22,7 +22,6 @@
android:layout_gravity="top|end"
android:contentDescription="@string/expand_button_content_description_collapsed"
android:padding="16dp"
- android:visibility="gone"
>
<LinearLayout
@@ -40,6 +39,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:gravity="center_vertical"
android:paddingStart="8dp"
+ android:visibility="gone"
/>
<ImageView
diff --git a/core/res/res/layout/notification_template_conversation_header.xml b/core/res/res/layout/notification_template_conversation_header.xml
index e01d803..389637eb 100644
--- a/core/res/res/layout/notification_template_conversation_header.xml
+++ b/core/res/res/layout/notification_template_conversation_header.xml
@@ -20,7 +20,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:paddingTop="16dp"
+ android:paddingTop="20dp"
>
<TextView
@@ -42,8 +42,6 @@
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
android:singleLine="true"
android:visibility="gone"
/>
@@ -54,10 +52,8 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
android:singleLine="true"
android:visibility="gone"
/>
@@ -70,8 +66,6 @@
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
android:singleLine="true"
android:visibility="gone"
/>
@@ -81,9 +75,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
android:showRelative="true"
android:singleLine="true"
android:visibility="gone"
@@ -93,7 +85,6 @@
android:id="@+id/chronometer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
android:layout="@layout/notification_template_part_chronometer"
android:visibility="gone"
@@ -107,8 +98,6 @@
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
android:layout_marginEnd="@dimen/notification_conversation_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
- android:layout_gravity="center"
- android:paddingTop="1sp"
android:singleLine="true"
android:visibility="gone"
/>
@@ -117,9 +106,8 @@
android:id="@+id/verification_icon"
android:layout_width="@dimen/notification_verification_icon_size"
android:layout_height="@dimen/notification_verification_icon_size"
- android:layout_gravity="center"
android:layout_marginStart="4dp"
- android:paddingTop="2dp"
+ android:baseline="10dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_notifications_alerted"
android:visibility="gone"
@@ -130,9 +118,7 @@
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
- android:paddingTop="1sp"
android:showRelative="true"
android:singleLine="true"
android:visibility="gone"
@@ -142,11 +128,10 @@
android:id="@+id/feedback"
android:layout_width="@dimen/notification_feedback_size"
android:layout_height="@dimen/notification_feedback_size"
- android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:background="?android:selectableItemBackgroundBorderless"
android:contentDescription="@string/notification_feedback_indicator"
- android:paddingTop="2dp"
+ android:baseline="13dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_feedback_indicator"
android:visibility="gone"
@@ -157,21 +142,19 @@
android:layout_width="@dimen/notification_phishing_alert_size"
android:layout_height="@dimen/notification_phishing_alert_size"
android:layout_marginStart="4dp"
- android:paddingTop="2dp"
+ android:baseline="10dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_dialog_alert_material"
android:visibility="gone"
android:contentDescription="@string/notification_phishing_alert_content_description"
/>
-
<ImageView
android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
- android:layout_gravity="center"
android:layout_marginStart="4dp"
- android:paddingTop="2dp"
+ android:baseline="10dp"
android:scaleType="fitCenter"
android:visibility="gone"
android:contentDescription="@string/notification_work_profile_content_description"
@@ -181,10 +164,9 @@
android:id="@+id/alerted_icon"
android:layout_width="@dimen/notification_alerted_size"
android:layout_height="@dimen/notification_alerted_size"
- android:layout_gravity="center"
android:layout_marginStart="4dp"
+ android:baseline="10dp"
android:contentDescription="@string/notification_alerted_content_description"
- android:paddingTop="2dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_notifications_alerted"
android:visibility="gone"
diff --git a/core/res/res/layout/notification_template_conversation_icon_container.xml b/core/res/res/layout/notification_template_conversation_icon_container.xml
index e9ec7ce..a88ff0d 100644
--- a/core/res/res/layout/notification_template_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_template_conversation_icon_container.xml
@@ -23,8 +23,8 @@
android:gravity="start|top"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
+ android:paddingTop="20dp"
+ android:paddingBottom="16dp"
android:importantForAccessibility="no"
>
diff --git a/core/res/res/layout/notification_template_material_call.xml b/core/res/res/layout/notification_template_material_call.xml
index 5d9e761..1b3bd26 100644
--- a/core/res/res/layout/notification_template_material_call.xml
+++ b/core/res/res/layout/notification_template_material_call.xml
@@ -29,7 +29,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="80dp"
+ android:layout_height="88dp"
android:orientation="horizontal"
>
diff --git a/core/res/res/layout/notification_top_line_views.xml b/core/res/res/layout/notification_top_line_views.xml
index 88bcc4d..8284279 100644
--- a/core/res/res/layout/notification_top_line_views.xml
+++ b/core/res/res/layout/notification_top_line_views.xml
@@ -113,11 +113,10 @@
android:layout_width="@dimen/notification_feedback_size"
android:layout_height="@dimen/notification_feedback_size"
android:layout_marginStart="@dimen/notification_header_separating_margin"
- android:layout_gravity="center"
+ android:baseline="13dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_feedback_indicator"
android:background="?android:selectableItemBackgroundBorderless"
- android:paddingTop="2dp"
android:visibility="gone"
android:contentDescription="@string/notification_feedback_indicator"
/>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c51b2d8..588ae79 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -826,7 +826,7 @@
that created the task, and therefore there will only be one instance of this activity
in a task. In constrast to the {@code singleTask} launch mode, this activity can be
started in multiple instances in different tasks if the
- {@code FLAG_ACTIVITY_MULTIPLE_TASK} is set.-->
+ {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set.-->
<enum name="singleInstancePerTask" value="4" />
</attr>
<!-- Specify the orientation an activity should be run in. If not
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 906a740..f24d663 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1772,6 +1772,9 @@
<!-- Add algorithm here -->
</string-array>
+ <!-- Boolean indicating if placing the phone face down will result in a screen off. -->
+ <bool name="config_flipToScreenOffEnabled">true</bool>
+
<!-- Boolean indicating if current platform supports bluetooth SCO for off call
use cases -->
<bool name="config_bluetooth_sco_off_call">true</bool>
@@ -4697,6 +4700,11 @@
-->
<color name="config_letterboxBackgroundColor">#000</color>
+ <!-- Horizonal position of a center of the letterboxed app window.
+ 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
+ or > 1, it is ignored and central positionis used (0.5). -->
+ <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
+
<!-- If true, hide the display cutout with display area -->
<bool name="config_hideDisplayCutoutWithDisplayArea">false</bool>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 2bdf91f..0e436e3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -758,23 +758,23 @@
<dimen name="notification_grayscale_icon_max_size">256dp</dimen>
<dimen name="messaging_avatar_size">36dp</dimen>
- <dimen name="conversation_avatar_size">52dp</dimen>
+ <dimen name="conversation_avatar_size">48dp</dimen>
<!-- start margin of the icon circle in the conversation's skin of the header -->
<dimen name="conversation_icon_circle_start">28dp</dimen>
<!-- Start of the content in the conversation template -->
<dimen name="conversation_content_start">80dp</dimen>
<!-- Height of the expand button in the conversation layout -->
- <dimen name="conversation_expand_button_height">80dp</dimen>
+ <dimen name="conversation_expand_button_height">88dp</dimen>
<!-- this is the margin between the Conversation image and the content -->
<dimen name="conversation_image_start_margin">12dp</dimen>
<!-- Side margins of the conversation badge in relation to the conversation icon -->
- <dimen name="conversation_badge_side_margin">36dp</dimen>
+ <dimen name="conversation_badge_side_margin">32dp</dimen>
<!-- size of the notification badge when applied to the conversation icon -->
<dimen name="conversation_icon_size_badged">20dp</dimen>
<!-- size of the conversation avatar in an expanded group -->
<dimen name="conversation_avatar_size_group_expanded">@dimen/messaging_avatar_size</dimen>
<!-- size of the face pile icons -->
- <dimen name="conversation_face_pile_avatar_size">@dimen/messaging_avatar_size</dimen>
+ <dimen name="conversation_face_pile_avatar_size">32dp</dimen>
<!-- size of the face pile icons when the group is expanded -->
<dimen name="conversation_face_pile_avatar_size_group_expanded">25dp</dimen>
<!-- Side margins of the conversation badge in relation to the conversation icon when the group is expanded-->
@@ -795,7 +795,7 @@
<dimen name="importance_ring_size">20dp</dimen>
<!-- The top padding of the conversation icon container in the regular state-->
- <dimen name="conversation_icon_container_top_padding">12dp</dimen>
+ <dimen name="conversation_icon_container_top_padding">20dp</dimen>
<!-- The top padding of the conversation icon container when the avatar is small-->
<dimen name="conversation_icon_container_top_padding_small_avatar">9dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 90c0691d..5715fab 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -275,6 +275,7 @@
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
+ <java-symbol type="bool" name="config_flipToScreenOffEnabled" />
<java-symbol type="bool" name="config_bluetooth_sco_off_call" />
<java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" />
<java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
@@ -4185,6 +4186,7 @@
<java-symbol type="dimen" name="config_letterboxBackgroundWallaperDarkScrimAlpha" />
<java-symbol type="integer" name="config_letterboxBackgroundType" />
<java-symbol type="color" name="config_letterboxBackgroundColor" />
+ <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
@@ -4339,4 +4341,6 @@
<java-symbol type="drawable" name="ic_accessibility_24dp" />
<java-symbol type="string" name="view_and_control_notification_title" />
<java-symbol type="string" name="view_and_control_notification_content" />
+
+ <java-symbol type="layout" name="notification_expand_button"/>
</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml
new file mode 100644
index 0000000..1f57318
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#d14d2c">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11.17,19.5h-0.83l0.82,-5.83 -4.18,0.01 5.85,-9.17h0.83l-0.84,5.84h4.17l-5.82,9.15z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml
new file mode 100644
index 0000000..70aac32
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_calculate_24.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#269e5c">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M19,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V5h14V19z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.25,7.72h5v1.5h-5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,15.75h5v1.5h-5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13,13.25h5v1.5h-5z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M8,18l1.5,0l0,-2l2,0l0,-1.5l-2,0l0,-2l-1.5,0l0,2l-2,0l0,1.5l2,0z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.09,10.95l1.41,-1.41l1.41,1.41l1.06,-1.06l-1.41,-1.42l1.41,-1.41l-1.06,-1.06l-1.41,1.41l-1.41,-1.41l-1.06,1.06l1.41,1.41l-1.41,1.42z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml
new file mode 100644
index 0000000..39f9689
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="#d14d2c">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9L22,7h-2L20,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2v-2h-2L20,9h2zM18,19L4,19L4,5h14v14zM6,13h5v4L6,17v-4zM12,7h4v3h-4L12,7zM6,7h5v5L6,12L6,7zM12,11h4v6h-4v-6z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.xml
new file mode 100644
index 0000000..9cae545
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_timer_24.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15,3L9,3L9,1h6v2zM11,14h2L13,8h-2v6zM21,13.01c0,4.97 -4.02,9 -9,9s-9,-4.03 -9,-9 4.03,-9 9,-9c2.12,0 4.07,0.74 5.62,1.98l1.42,-1.42c0.51,0.42 0.98,0.9 1.41,1.41L19.03,7.4C20.26,8.93 21,10.89 21,13.01zM19,13.01c0,-3.87 -3.13,-7 -7,-7s-7,3.13 -7,7 3.13,7 7,7 7,-3.13 7,-7z"/>
+</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
index 1ced825..98fc581 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_consumer_entry_layout.xml
@@ -25,6 +25,13 @@
android:paddingTop="8dp"
android:paddingBottom="8dp">
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dp"/>
+
<TextView
android:id="@+id/title"
android:layout_width="0dp"
@@ -34,16 +41,18 @@
<TextView
android:id="@+id/amount"
- android:layout_width="0dp"
- android:layout_weight="0.7"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
android:gravity="right"
+ android:maxLines="1"
android:textAppearance="@style/TextAppearanceBody"/>
<TextView
android:id="@+id/percent"
- android:layout_width="64dp"
+ android:layout_width="76dp"
android:layout_height="wrap_content"
android:gravity="right"
+ android:maxLines="1"
android:textAppearance="@style/TextAppearanceBody"/>
</LinearLayout>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
index e58a08f..24d193c4 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/layout/battery_stats_viewer_layout.xml
@@ -43,10 +43,40 @@
android:paddingEnd="10dp">
<include layout="@layout/battery_consumer_info_layout"/>
-
</LinearLayout>
+
</androidx.cardview.widget.CardView>
+
+ <LinearLayout
+ android:id="@+id/headings"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="2dp"
+ android:paddingBottom="4dp">
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"/>
+ <TextView
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:paddingEnd="10dp"
+ android:text="Total"/>
+ <TextView
+ android:layout_width="100dp"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:paddingEnd="30dp"
+ android:text="Apps"/>
+ </LinearLayout>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@android:color/darker_gray"/>
+
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/battery_consumer_data_view"
android:layout_width="match_parent"
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
new file mode 100644
index 0000000..2dbe48b
--- /dev/null
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?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>
+ <color name="battery_consumer_bg_power_profile">#ffffff</color>
+ <color name="battery_consumer_bg_measured_energy">#fff5eb</color>
+</resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index 78569bd..f7d7098 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -18,32 +18,21 @@
import android.content.Context;
import android.os.BatteryConsumer;
-import android.os.BatteryStats;
import android.os.BatteryUsageStats;
-import android.os.Process;
import android.os.SystemBatteryConsumer;
import android.os.UidBatteryConsumer;
import android.os.UserHandle;
-
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
+import android.util.DebugUtils;
import java.util.ArrayList;
import java.util.List;
public class BatteryConsumerData {
- private static final String PACKAGE_CALENDAR_PROVIDER = "com.android.providers.calendar";
- private static final String PACKAGE_MEDIA_PROVIDER = "com.android.providers.media";
- private static final String PACKAGE_SYSTEMUI = "com.android.systemui";
- private static final String[] PACKAGES_SYSTEM = {PACKAGE_MEDIA_PROVIDER,
- PACKAGE_CALENDAR_PROVIDER, PACKAGE_SYSTEMUI};
-
- // Unit conversion:
- // mAh = uC * (1/1000)(milli/micro) * (1/3600)(hours/second)
- private static final double UC_2_MAH = (1.0 / 1000) * (1.0 / 3600);
enum EntryType {
- POWER,
+ POWER_MODELED,
+ POWER_MEASURED,
+ POWER_CUSTOM,
DURATION,
}
@@ -52,268 +41,155 @@
public EntryType entryType;
public double value;
public double total;
+ public boolean isSystemBatteryConsumer;
}
private final BatteryConsumerInfoHelper.BatteryConsumerInfo mBatteryConsumerInfo;
private final List<Entry> mEntries = new ArrayList<>();
- public BatteryConsumerData(Context context, BatteryStatsHelper batteryStatsHelper,
+ public BatteryConsumerData(Context context,
List<BatteryUsageStats> batteryUsageStatsList, String batteryConsumerId) {
BatteryUsageStats batteryUsageStats = batteryUsageStatsList.get(0);
- BatteryUsageStats powerProfileModeledUsageStats = batteryUsageStatsList.get(1);
- List<BatterySipper> usageList = batteryStatsHelper.getUsageList();
- BatteryStats batteryStats = batteryStatsHelper.getStats();
+ BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1);
- double totalPowerMah = 0;
- double totalSmearedPowerMah = 0;
- double totalPowerExcludeSystemMah = 0;
- double totalScreenPower = 0;
- double totalProportionalSmearMah = 0;
- double totalCpuPowerMah = 0;
- double totalSystemServiceCpuPowerMah = 0;
- double totalUsagePowerMah = 0;
- double totalWakeLockPowerMah = 0;
- double totalMobileRadioPowerMah = 0;
- double totalWifiPowerMah = 0;
- double totalBluetoothPowerMah = 0;
- double totalGpsPowerMah = 0;
- double totalCameraPowerMah = 0;
- double totalFlashlightPowerMah = 0;
- double totalSensorPowerMah = 0;
- double totalAudioPowerMah = 0;
- double totalVideoPowerMah = 0;
+ BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats,
+ batteryConsumerId);
+ BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer(
+ modeledBatteryUsageStats, batteryConsumerId);
- long totalCpuTimeMs = 0;
- long totalCpuFgTimeMs = 0;
- long totalWakeLockTimeMs = 0;
- long totalWifiRunningTimeMs = 0;
- long totalBluetoothRunningTimeMs = 0;
- long totalGpsTimeMs = 0;
- long totalCameraTimeMs = 0;
- long totalFlashlightTimeMs = 0;
- long totalAudioTimeMs = 0;
- long totalVideoTimeMs = 0;
-
- BatterySipper requestedBatterySipper = null;
- for (BatterySipper sipper : usageList) {
- if (sipper.drainType == BatterySipper.DrainType.SCREEN) {
- totalScreenPower = sipper.sumPower();
- }
-
- if (batteryConsumerId(sipper).equals(batteryConsumerId)) {
- requestedBatterySipper = sipper;
- }
-
- totalPowerMah += sipper.sumPower();
- totalSmearedPowerMah += sipper.totalSmearedPowerMah;
- totalProportionalSmearMah += sipper.proportionalSmearMah;
-
- if (!isSystemSipper(sipper)) {
- totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah;
- }
-
- totalCpuPowerMah += sipper.cpuPowerMah;
- totalSystemServiceCpuPowerMah += sipper.systemServiceCpuPowerMah;
- totalUsagePowerMah += sipper.usagePowerMah;
- totalWakeLockPowerMah += sipper.wakeLockPowerMah;
- totalMobileRadioPowerMah += sipper.mobileRadioPowerMah;
- totalWifiPowerMah += sipper.wifiPowerMah;
- totalBluetoothPowerMah += sipper.bluetoothPowerMah;
- totalGpsPowerMah += sipper.gpsPowerMah;
- totalCameraPowerMah += sipper.cameraPowerMah;
- totalFlashlightPowerMah += sipper.flashlightPowerMah;
- totalSensorPowerMah += sipper.sensorPowerMah;
- totalAudioPowerMah += sipper.audioPowerMah;
- totalVideoPowerMah += sipper.videoPowerMah;
-
- totalCpuTimeMs += sipper.cpuTimeMs;
- totalCpuFgTimeMs += sipper.cpuFgTimeMs;
- totalWakeLockTimeMs += sipper.wakeLockTimeMs;
- totalWifiRunningTimeMs += sipper.wifiRunningTimeMs;
- totalBluetoothRunningTimeMs += sipper.bluetoothRunningTimeMs;
- totalGpsTimeMs += sipper.gpsTimeMs;
- totalCameraTimeMs += sipper.cameraTimeMs;
- totalFlashlightTimeMs += sipper.flashlightTimeMs;
- totalAudioTimeMs += sipper.audioTimeMs;
- totalVideoTimeMs += sipper.videoTimeMs;
- }
-
- BatteryConsumer requestedBatteryConsumer = null;
-
- for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
- if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
- requestedBatteryConsumer = consumer;
- }
- }
-
- double totalModeledCpuPowerMah = 0;
- BatteryConsumer requestedBatteryConsumerPowerProfileModeled = null;
- for (BatteryConsumer consumer : powerProfileModeledUsageStats.getUidBatteryConsumers()) {
- if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
- requestedBatteryConsumerPowerProfileModeled = consumer;
- }
-
- totalModeledCpuPowerMah += consumer.getConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU);
- }
-
- if (requestedBatterySipper == null) {
+ if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) {
mBatteryConsumerInfo = null;
return;
}
- if (requestedBatteryConsumer == null) {
- for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
- if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
- requestedBatteryConsumer = consumer;
- break;
- }
- }
- }
-
mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo(
- context.getPackageManager(), requestedBatterySipper);
- long totalScreenMeasuredChargeUC =
- batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
- long uidScreenMeasuredChargeUC =
- requestedBatterySipper.uidObj.getScreenOnMeasuredBatteryConsumptionUC();
+ context.getPackageManager(), requestedBatteryConsumer);
- addEntry("Total power", EntryType.POWER,
- requestedBatterySipper.totalSmearedPowerMah, totalSmearedPowerMah);
- maybeAddMeasuredEnergyEntry(requestedBatterySipper.drainType, batteryStats);
+ double[] totalPowerByComponentMah = new double[BatteryConsumer.POWER_COMPONENT_COUNT];
+ double[] totalModeledPowerByComponentMah =
+ new double[BatteryConsumer.POWER_COMPONENT_COUNT];
+ long[] totalDurationByComponentMs = new long[BatteryConsumer.TIME_COMPONENT_COUNT];
+ final int customComponentCount =
+ requestedBatteryConsumer.getCustomPowerComponentCount();
+ double[] totalCustomPowerByComponentMah = new double[customComponentCount];
- addEntry("... excluding system", EntryType.POWER,
- requestedBatterySipper.totalSmearedPowerMah, totalPowerExcludeSystemMah);
- addEntry("Screen, smeared", EntryType.POWER,
- requestedBatterySipper.screenPowerMah, totalScreenPower);
- if (uidScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE
- && totalScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
- final double measuredCharge = UC_2_MAH * uidScreenMeasuredChargeUC;
- final double totalMeasuredCharge = UC_2_MAH * totalScreenMeasuredChargeUC;
- addEntry("Screen, measured", EntryType.POWER,
- measuredCharge, totalMeasuredCharge);
- }
- addEntry("Other, smeared", EntryType.POWER,
- requestedBatterySipper.proportionalSmearMah, totalProportionalSmearMah);
- addEntry("Excluding smeared", EntryType.POWER,
- requestedBatterySipper.totalPowerMah, totalPowerMah);
- if (requestedBatteryConsumer != null) {
- addEntry("CPU", EntryType.POWER,
- requestedBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU),
- totalCpuPowerMah);
- if (requestedBatteryConsumerPowerProfileModeled != null) {
- addEntry("CPU (modeled)", EntryType.POWER,
- requestedBatteryConsumerPowerProfileModeled.getConsumedPower(
- BatteryConsumer.POWER_COMPONENT_CPU),
- totalModeledCpuPowerMah);
- }
- } else {
- addEntry("CPU (sipper)", EntryType.POWER,
- requestedBatterySipper.cpuPowerMah, totalCpuPowerMah);
- }
- addEntry("System services", EntryType.POWER,
- requestedBatterySipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah);
- if (requestedBatteryConsumer != null) {
- addEntry("Usage", EntryType.POWER,
- requestedBatteryConsumer.getConsumedPower(
- BatteryConsumer.POWER_COMPONENT_USAGE), totalUsagePowerMah);
- } else {
- addEntry("Usage (sipper)", EntryType.POWER,
- requestedBatterySipper.usagePowerMah, totalUsagePowerMah);
- }
- addEntry("Wake lock", EntryType.POWER,
- requestedBatterySipper.wakeLockPowerMah, totalWakeLockPowerMah);
- addEntry("Mobile radio", EntryType.POWER,
- requestedBatterySipper.mobileRadioPowerMah, totalMobileRadioPowerMah);
- addEntry("WiFi", EntryType.POWER,
- requestedBatterySipper.wifiPowerMah, totalWifiPowerMah);
- addEntry("Bluetooth", EntryType.POWER,
- requestedBatterySipper.bluetoothPowerMah, totalBluetoothPowerMah);
- addEntry("GPS", EntryType.POWER,
- requestedBatterySipper.gpsPowerMah, totalGpsPowerMah);
- addEntry("Camera", EntryType.POWER,
- requestedBatterySipper.cameraPowerMah, totalCameraPowerMah);
- addEntry("Flashlight", EntryType.POWER,
- requestedBatterySipper.flashlightPowerMah, totalFlashlightPowerMah);
- addEntry("Sensors", EntryType.POWER,
- requestedBatterySipper.sensorPowerMah, totalSensorPowerMah);
- addEntry("Audio", EntryType.POWER,
- requestedBatterySipper.audioPowerMah, totalAudioPowerMah);
- addEntry("Video", EntryType.POWER,
- requestedBatterySipper.videoPowerMah, totalVideoPowerMah);
+ computeTotalPower(batteryUsageStats, totalPowerByComponentMah);
+ computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah);
+ computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah);
+ computeTotalDuration(batteryUsageStats, totalDurationByComponentMs);
- addEntry("CPU time", EntryType.DURATION,
- requestedBatterySipper.cpuTimeMs, totalCpuTimeMs);
- addEntry("CPU foreground time", EntryType.DURATION,
- requestedBatterySipper.cpuFgTimeMs, totalCpuFgTimeMs);
- addEntry("Wake lock time", EntryType.DURATION,
- requestedBatterySipper.wakeLockTimeMs, totalWakeLockTimeMs);
- addEntry("WiFi running time", EntryType.DURATION,
- requestedBatterySipper.wifiRunningTimeMs, totalWifiRunningTimeMs);
- addEntry("Bluetooth time", EntryType.DURATION,
- requestedBatterySipper.bluetoothRunningTimeMs, totalBluetoothRunningTimeMs);
- addEntry("GPS time", EntryType.DURATION,
- requestedBatterySipper.gpsTimeMs, totalGpsTimeMs);
- addEntry("Camera time", EntryType.DURATION,
- requestedBatterySipper.cameraTimeMs, totalCameraTimeMs);
- addEntry("Flashlight time", EntryType.DURATION,
- requestedBatterySipper.flashlightTimeMs, totalFlashlightTimeMs);
- addEntry("Audio time", EntryType.DURATION,
- requestedBatterySipper.audioTimeMs, totalAudioTimeMs);
- addEntry("Video time", EntryType.DURATION,
- requestedBatterySipper.videoTimeMs, totalVideoTimeMs);
- }
-
- private boolean isSystemSipper(BatterySipper sipper) {
- final int uid = sipper.uidObj == null ? -1 : sipper.getUid();
- if (uid >= Process.ROOT_UID && uid < Process.FIRST_APPLICATION_UID) {
- return true;
- } else if (sipper.mPackages != null) {
- for (final String packageName : sipper.mPackages) {
- for (final String systemPackage : PACKAGES_SYSTEM) {
- if (systemPackage.equals(packageName)) {
- return true;
- }
- }
+ for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT; component++) {
+ final String metricTitle = getPowerMetricTitle(component);
+ final int powerModel = requestedBatteryConsumer.getPowerModel(component);
+ if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
+ addEntry(metricTitle, EntryType.POWER_MODELED,
+ requestedBatteryConsumer.getConsumedPower(component),
+ totalPowerByComponentMah[component],
+ mBatteryConsumerInfo.isSystemBatteryConsumer);
+ } else {
+ addEntry(metricTitle + " (measured)", EntryType.POWER_MEASURED,
+ requestedBatteryConsumer.getConsumedPower(component),
+ totalPowerByComponentMah[component],
+ mBatteryConsumerInfo.isSystemBatteryConsumer);
+ addEntry(metricTitle + " (modeled)", EntryType.POWER_MODELED,
+ requestedModeledBatteryConsumer.getConsumedPower(component),
+ totalModeledPowerByComponentMah[component],
+ mBatteryConsumerInfo.isSystemBatteryConsumer);
}
}
- return false;
+ for (int component = 0; component < customComponentCount; component++) {
+ final String name = requestedBatteryConsumer.getCustomPowerComponentName(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
+ addEntry(name + " (custom)", EntryType.POWER_CUSTOM,
+ requestedBatteryConsumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
+ totalCustomPowerByComponentMah[component],
+ mBatteryConsumerInfo.isSystemBatteryConsumer);
+ }
+
+ for (int component = 0; component < BatteryConsumer.TIME_COMPONENT_COUNT; component++) {
+ final String metricTitle = getTimeMetricTitle(component);
+ addEntry(metricTitle, EntryType.DURATION,
+ requestedBatteryConsumer.getUsageDurationMillis(component),
+ totalDurationByComponentMs[component],
+ mBatteryConsumerInfo.isSystemBatteryConsumer);
+ }
}
- private void addEntry(String title, EntryType entryType, double amount, double totalAmount) {
+ private BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats,
+ String batteryConsumerId) {
+ for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+ return consumer;
+ }
+ }
+ for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) {
+ if (batteryConsumerId(consumer).equals(batteryConsumerId)) {
+ return consumer;
+ }
+ }
+ return null;
+ }
+
+ static String getPowerMetricTitle(int componentId) {
+ final String componentName = DebugUtils.constantToString(BatteryConsumer.class,
+ "POWER_COMPONENT_", componentId);
+ return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ')
+ + " power";
+ }
+
+ static String getTimeMetricTitle(int componentId) {
+ final String componentName = DebugUtils.constantToString(BatteryConsumer.class,
+ "TIME_COMPONENT_", componentId);
+ return componentName.charAt(0) + componentName.substring(1).toLowerCase().replace('_', ' ')
+ + " time";
+ }
+
+ private void computeTotalPower(BatteryUsageStats batteryUsageStats,
+ double[] powerByComponentMah) {
+ for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ for (int component = 0; component < BatteryConsumer.POWER_COMPONENT_COUNT;
+ component++) {
+ powerByComponentMah[component] += consumer.getConsumedPower(component);
+ }
+ }
+ }
+
+ private void computeTotalDuration(BatteryUsageStats batteryUsageStats,
+ long[] durationByComponentMs) {
+ for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ for (int component = 0; component < BatteryConsumer.TIME_COMPONENT_COUNT;
+ component++) {
+ durationByComponentMs[component] += consumer.getUsageDurationMillis(component);
+ }
+ }
+ }
+
+ private void computeTotalPowerForCustomComponent(
+ BatteryUsageStats batteryUsageStats, double[] powerByComponentMah) {
+ for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ final int customComponentCount = consumer.getCustomPowerComponentCount();
+ for (int component = 0;
+ component < Math.min(customComponentCount, powerByComponentMah.length);
+ component++) {
+ powerByComponentMah[component] += consumer.getConsumedPowerForCustomComponent(
+ BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
+ }
+ }
+ }
+
+ private void addEntry(String title, EntryType entryType, double amount, double totalAmount,
+ boolean isSystemBatteryConsumer) {
Entry entry = new Entry();
entry.title = title;
entry.entryType = entryType;
entry.value = amount;
entry.total = totalAmount;
+ entry.isSystemBatteryConsumer = isSystemBatteryConsumer;
mEntries.add(entry);
}
- private void maybeAddMeasuredEnergyEntry(BatterySipper.DrainType drainType,
- BatteryStats batteryStats) {
- switch (drainType) {
- case AMBIENT_DISPLAY:
- final long totalDozeMeasuredChargeUC =
- batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
- if (totalDozeMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
- final double measuredCharge = UC_2_MAH * totalDozeMeasuredChargeUC;
- addEntry("Measured ambient display power", EntryType.POWER, measuredCharge,
- measuredCharge);
- }
- break;
- case SCREEN:
- final long totalScreenMeasuredChargeUC =
- batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
- if (totalScreenMeasuredChargeUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
- final double measuredCharge = UC_2_MAH * totalScreenMeasuredChargeUC;
- addEntry("Measured screen power", EntryType.POWER, measuredCharge,
- measuredCharge);
- }
- break;
- }
- }
-
public BatteryConsumerInfoHelper.BatteryConsumerInfo getBatteryConsumerInfo() {
return mBatteryConsumerInfo;
}
@@ -322,13 +198,9 @@
return mEntries;
}
- public static String batteryConsumerId(BatterySipper sipper) {
- return sipper.drainType + "|" + sipper.userId + "|" + sipper.getUid();
- }
-
public static String batteryConsumerId(BatteryConsumer consumer) {
if (consumer instanceof UidBatteryConsumer) {
- return BatterySipper.DrainType.APP + "|"
+ return "APP|"
+ UserHandle.getUserId(((UidBatteryConsumer) consumer).getUid()) + "|"
+ ((UidBatteryConsumer) consumer).getUid();
} else if (consumer instanceof SystemBatteryConsumer) {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
index 8ee6c604..6288e0b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerInfoHelper.java
@@ -18,14 +18,14 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.BatteryConsumer;
import android.os.Process;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
+import android.util.DebugUtils;
import androidx.annotation.NonNull;
-import com.android.internal.os.BatterySipper;
-
-import java.util.Locale;
-
class BatteryConsumerInfoHelper {
private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
@@ -37,111 +37,79 @@
public ApplicationInfo iconInfo;
public CharSequence packages;
public CharSequence details;
+ public boolean isSystemBatteryConsumer;
}
@NonNull
public static BatteryConsumerInfo makeBatteryConsumerInfo(PackageManager packageManager,
- @NonNull BatterySipper sipper) {
+ @NonNull BatteryConsumer batteryConsumer) {
BatteryConsumerInfo info = new BatteryConsumerInfo();
- info.id = BatteryConsumerData.batteryConsumerId(sipper);
- sipper.sumPower();
- info.powerMah = sipper.totalSmearedPowerMah;
- switch (sipper.drainType) {
- case APP: {
- int uid = sipper.getUid();
- info.details = String.format("UID: %d", uid);
- String packageWithHighestDrain = sipper.packageWithHighestDrain;
- if (uid == Process.ROOT_UID) {
- info.label = "<root>";
- } else {
- String[] packages = packageManager.getPackagesForUid(uid);
- String primaryPackageName = null;
- if (uid == Process.SYSTEM_UID) {
- primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
- } else if (packages != null) {
- for (String name : packages) {
- primaryPackageName = name;
- if (name.equals(packageWithHighestDrain)) {
- break;
- }
- }
- }
+ info.id = BatteryConsumerData.batteryConsumerId(batteryConsumer);
+ info.powerMah = batteryConsumer.getConsumedPower();
- if (primaryPackageName != null) {
- try {
- ApplicationInfo applicationInfo =
- packageManager.getApplicationInfo(primaryPackageName, 0);
- info.label = applicationInfo.loadLabel(packageManager);
- info.iconInfo = applicationInfo;
- } catch (PackageManager.NameNotFoundException e) {
- info.label = primaryPackageName;
+ if (batteryConsumer instanceof UidBatteryConsumer) {
+ final UidBatteryConsumer uidBatteryConsumer = (UidBatteryConsumer) batteryConsumer;
+ int uid = uidBatteryConsumer.getUid();
+ info.details = String.format("UID: %d", uid);
+ String packageWithHighestDrain = uidBatteryConsumer.getPackageWithHighestDrain();
+ if (uid == Process.ROOT_UID) {
+ info.label = "<root>";
+ } else {
+ String[] packages = packageManager.getPackagesForUid(uid);
+ String primaryPackageName = null;
+ if (uid == Process.SYSTEM_UID) {
+ primaryPackageName = SYSTEM_SERVER_PACKAGE_NAME;
+ } else if (packages != null) {
+ for (String name : packages) {
+ primaryPackageName = name;
+ if (name.equals(packageWithHighestDrain)) {
+ break;
}
- } else if (packageWithHighestDrain != null) {
- info.label = packageWithHighestDrain;
- }
-
- if (packages != null && packages.length > 0) {
- StringBuilder sb = new StringBuilder();
- if (primaryPackageName != null) {
- sb.append(primaryPackageName);
- }
- for (String packageName : packages) {
- if (packageName.equals(primaryPackageName)) {
- continue;
- }
-
- if (sb.length() != 0) {
- sb.append(", ");
- }
- sb.append(packageName);
- }
-
- info.packages = sb;
}
}
- break;
+
+ if (primaryPackageName != null) {
+ try {
+ ApplicationInfo applicationInfo =
+ packageManager.getApplicationInfo(primaryPackageName, 0);
+ info.label = applicationInfo.loadLabel(packageManager);
+ info.iconInfo = applicationInfo;
+ } catch (PackageManager.NameNotFoundException e) {
+ info.label = primaryPackageName;
+ }
+ } else if (packageWithHighestDrain != null) {
+ info.label = packageWithHighestDrain;
+ }
+
+ if (packages != null && packages.length > 0) {
+ StringBuilder sb = new StringBuilder();
+ if (primaryPackageName != null) {
+ sb.append(primaryPackageName);
+ }
+ for (String packageName : packages) {
+ if (packageName.equals(primaryPackageName)) {
+ continue;
+ }
+
+ if (sb.length() != 0) {
+ sb.append(", ");
+ }
+ sb.append(packageName);
+ }
+
+ info.packages = sb;
+ }
}
- case USER:
- info.label = "User";
- info.details = String.format(Locale.getDefault(), "User ID: %d", sipper.userId);
- break;
- case AMBIENT_DISPLAY:
- info.label = "Ambient display";
- break;
- case BLUETOOTH:
- info.label = "Bluetooth";
- break;
- case CAMERA:
- info.label = "Camera";
- break;
- case CELL:
- info.label = "Cell";
- break;
- case FLASHLIGHT:
- info.label = "Flashlight";
- break;
- case IDLE:
- info.label = "Idle";
- break;
- case MEMORY:
- info.label = "Memory";
- break;
- case OVERCOUNTED:
- info.label = "Overcounted";
- break;
- case PHONE:
- info.label = "Phone";
- break;
- case SCREEN:
- info.label = "Screen";
- break;
- case UNACCOUNTED:
- info.label = "Unaccounted";
- break;
- case WIFI:
- info.label = "WiFi";
- break;
+ } else if (batteryConsumer instanceof SystemBatteryConsumer) {
+ final SystemBatteryConsumer systemBatteryConsumer =
+ (SystemBatteryConsumer) batteryConsumer;
+ final int drainType = systemBatteryConsumer.getDrainType();
+ String name = DebugUtils.constantToString(SystemBatteryConsumer.class, "DRAIN_TYPE_",
+ drainType);
+ info.label = name.charAt(0) + name.substring(1).toLowerCase().replace('_', ' ');
+ info.isSystemBatteryConsumer = true;
}
+
// Default the app icon to System Server. This includes root, dex2oat and other UIDs.
if (info.iconInfo == null) {
try {
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
index bb11fd5..4922087 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerPickerFragment.java
@@ -18,10 +18,11 @@
import android.content.Context;
import android.content.pm.PackageManager;
-import android.os.BatteryStats;
+import android.os.BatteryStatsManager;
+import android.os.BatteryUsageStats;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
+import android.os.SystemBatteryConsumer;
+import android.os.UidBatteryConsumer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,8 +38,6 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.frameworks.core.batterystatsviewer.BatteryConsumerInfoHelper.BatteryConsumerInfo;
-import com.android.internal.os.BatterySipper;
-import com.android.internal.os.BatteryStatsHelper;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.ArrayList;
@@ -99,44 +98,39 @@
private static class BatteryConsumerListLoader extends
AsyncLoaderCompat<List<BatteryConsumerInfo>> {
- private final BatteryStatsHelper mStatsHelper;
private final int mPickerType;
- private final UserManager mUserManager;
+ private final BatteryStatsManager mBatteryStatsManager;
private final PackageManager mPackageManager;
BatteryConsumerListLoader(Context context, int pickerType) {
super(context);
- mUserManager = context.getSystemService(UserManager.class);
- mStatsHelper = new BatteryStatsHelper(context, false /* collectBatteryBroadcast */);
+ mBatteryStatsManager = context.getSystemService(BatteryStatsManager.class);
mPickerType = pickerType;
- mStatsHelper.create((Bundle) null);
- mStatsHelper.clearStats();
mPackageManager = context.getPackageManager();
}
@Override
public List<BatteryConsumerInfo> loadInBackground() {
+ final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats();
+
List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>();
-
- mStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.myUserId());
-
- final List<BatterySipper> usageList = mStatsHelper.getUsageList();
- for (BatterySipper sipper : usageList) {
- switch (mPickerType) {
- case PICKER_TYPE_APP:
- if (sipper.drainType != BatterySipper.DrainType.APP) {
- continue;
- }
- break;
- case PICKER_TYPE_DRAIN:
- default:
- if (sipper.drainType == BatterySipper.DrainType.APP) {
- continue;
- }
- }
-
- batteryConsumerList.add(
- BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager, sipper));
+ switch (mPickerType) {
+ case PICKER_TYPE_APP:
+ for (UidBatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) {
+ batteryConsumerList.add(
+ BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
+ consumer));
+ }
+ break;
+ case PICKER_TYPE_DRAIN:
+ default:
+ for (SystemBatteryConsumer consumer :
+ batteryUsageStats.getSystemBatteryConsumers()) {
+ batteryConsumerList.add(
+ BatteryConsumerInfoHelper.makeBatteryConsumerInfo(mPackageManager,
+ consumer));
+ }
+ break;
}
batteryConsumerList.sort(
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 4ead8ee..74d3fb3 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -18,13 +18,10 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.os.BatteryStats;
import android.os.BatteryStatsManager;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.Bundle;
-import android.os.UserHandle;
-import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -41,7 +38,6 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.internal.os.BatteryStatsHelper;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.Collections;
@@ -50,24 +46,24 @@
public class BatteryStatsViewerActivity extends ComponentActivity {
private static final int BATTERY_STATS_REFRESH_RATE_MILLIS = 60 * 1000;
- public static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
- public static final int LOADER_BATTERY_STATS_HELPER = 0;
- public static final int LOADER_BATTERY_USAGE_STATS = 1;
+ private static final int MILLIS_IN_MINUTE = 60000;
+ private static final String PREF_SELECTED_BATTERY_CONSUMER = "batteryConsumerId";
+ private static final int LOADER_BATTERY_USAGE_STATS = 1;
private BatteryStatsDataAdapter mBatteryStatsDataAdapter;
- private Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
+ private final Runnable mBatteryStatsRefresh = this::periodicBatteryStatsRefresh;
private SharedPreferences mSharedPref;
private String mBatteryConsumerId;
private TextView mTitleView;
private TextView mDetailsView;
private ImageView mIconView;
private TextView mPackagesView;
+ private View mHeadingsView;
private RecyclerView mBatteryConsumerDataView;
private View mLoadingView;
private View mEmptyView;
- private ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
+ private final ActivityResultLauncher<Void> mStartAppPicker = registerForActivityResult(
BatteryConsumerPickerActivity.CONTRACT, this::onApplicationSelected);
- private BatteryStatsHelper mBatteryStatsHelper;
private List<BatteryUsageStats> mBatteryUsageStats;
@Override
@@ -85,6 +81,7 @@
mDetailsView = findViewById(R.id.details);
mIconView = findViewById(android.R.id.icon);
mPackagesView = findViewById(R.id.packages);
+ mHeadingsView = findViewById(R.id.headings);
mBatteryConsumerDataView = findViewById(R.id.battery_consumer_data_view);
mBatteryConsumerDataView.setLayoutManager(new LinearLayoutManager(this));
@@ -139,55 +136,10 @@
private void loadBatteryStats() {
LoaderManager loaderManager = LoaderManager.getInstance(this);
- loaderManager.restartLoader(LOADER_BATTERY_STATS_HELPER, null,
- new BatteryStatsHelperLoaderCallbacks());
loaderManager.restartLoader(LOADER_BATTERY_USAGE_STATS, null,
new BatteryUsageStatsLoaderCallbacks());
}
- private static class BatteryStatsHelperLoader extends AsyncLoaderCompat<BatteryStatsHelper> {
- private final BatteryStatsHelper mBatteryStatsHelper;
- private final UserManager mUserManager;
-
- BatteryStatsHelperLoader(Context context) {
- super(context);
- mUserManager = context.getSystemService(UserManager.class);
- mBatteryStatsHelper = new BatteryStatsHelper(context,
- false /* collectBatteryBroadcast */);
- mBatteryStatsHelper.create((Bundle) null);
- mBatteryStatsHelper.clearStats();
- }
-
- @Override
- public BatteryStatsHelper loadInBackground() {
- mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED,
- UserHandle.myUserId());
- return mBatteryStatsHelper;
- }
-
- @Override
- protected void onDiscardResult(BatteryStatsHelper result) {
- }
- }
-
- private class BatteryStatsHelperLoaderCallbacks implements LoaderCallbacks<BatteryStatsHelper> {
- @NonNull
- @Override
- public Loader<BatteryStatsHelper> onCreateLoader(int id, Bundle args) {
- return new BatteryStatsHelperLoader(BatteryStatsViewerActivity.this);
- }
-
- @Override
- public void onLoadFinished(@NonNull Loader<BatteryStatsHelper> loader,
- BatteryStatsHelper batteryStatsHelper) {
- onBatteryStatsHelperLoaded(batteryStatsHelper);
- }
-
- @Override
- public void onLoaderReset(@NonNull Loader<BatteryStatsHelper> loader) {
- }
- }
-
private static class BatteryUsageStatsLoader extends
AsyncLoaderCompat<List<BatteryUsageStats>> {
private final BatteryStatsManager mBatteryStatsManager;
@@ -200,10 +152,13 @@
@Override
public List<BatteryUsageStats> loadInBackground() {
final BatteryUsageStatsQuery queryDefault =
- new BatteryUsageStatsQuery.Builder().build();
+ new BatteryUsageStatsQuery.Builder()
+ .includePowerModels()
+ .build();
final BatteryUsageStatsQuery queryPowerProfileModeledOnly =
new BatteryUsageStatsQuery.Builder()
.powerProfileModeledOnly()
+ .includePowerModels()
.build();
return mBatteryStatsManager.getBatteryUsageStats(
List.of(queryDefault, queryPowerProfileModeledOnly));
@@ -233,22 +188,13 @@
}
}
- public void onBatteryStatsHelperLoaded(BatteryStatsHelper batteryStatsHelper) {
- mBatteryStatsHelper = batteryStatsHelper;
- onBatteryStatsDataLoaded();
- }
-
private void onBatteryUsageStatsLoaded(List<BatteryUsageStats> batteryUsageStats) {
mBatteryUsageStats = batteryUsageStats;
onBatteryStatsDataLoaded();
}
public void onBatteryStatsDataLoaded() {
- if (mBatteryStatsHelper == null || mBatteryUsageStats == null) {
- return;
- }
-
- BatteryConsumerData batteryConsumerData = new BatteryConsumerData(this, mBatteryStatsHelper,
+ BatteryConsumerData batteryConsumerData = new BatteryConsumerData(this,
mBatteryUsageStats, mBatteryConsumerId);
BatteryConsumerInfoHelper.BatteryConsumerInfo
@@ -256,6 +202,7 @@
if (batteryConsumerInfo == null) {
mTitleView.setText("Battery consumer not found");
mPackagesView.setVisibility(View.GONE);
+ mHeadingsView.setVisibility(View.GONE);
} else {
mTitleView.setText(batteryConsumerInfo.label);
if (batteryConsumerInfo.details != null) {
@@ -273,6 +220,12 @@
} else {
mPackagesView.setVisibility(View.GONE);
}
+
+ if (batteryConsumerInfo.isSystemBatteryConsumer) {
+ mHeadingsView.setVisibility(View.VISIBLE);
+ } else {
+ mHeadingsView.setVisibility(View.GONE);
+ }
}
mBatteryStatsDataAdapter.setEntries(batteryConsumerData.getEntries());
@@ -290,6 +243,7 @@
private static class BatteryStatsDataAdapter extends
RecyclerView.Adapter<BatteryStatsDataAdapter.ViewHolder> {
public static class ViewHolder extends RecyclerView.ViewHolder {
+ public ImageView iconImageView;
public TextView titleTextView;
public TextView amountTextView;
public TextView percentTextView;
@@ -297,6 +251,7 @@
ViewHolder(View itemView) {
super(itemView);
+ iconImageView = itemView.findViewById(R.id.icon);
titleTextView = itemView.findViewById(R.id.title);
amountTextView = itemView.findViewById(R.id.amount);
percentTextView = itemView.findViewById(R.id.percent);
@@ -328,21 +283,56 @@
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
BatteryConsumerData.Entry entry = mEntries.get(position);
switch (entry.entryType) {
- case POWER:
+ case POWER_MODELED:
viewHolder.titleTextView.setText(entry.title);
viewHolder.amountTextView.setText(
String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+ viewHolder.iconImageView.setImageResource(R.drawable.gm_calculate_24);
+ viewHolder.itemView.setBackgroundResource(
+ R.color.battery_consumer_bg_power_profile);
+ break;
+ case POWER_MEASURED:
+ viewHolder.titleTextView.setText(entry.title);
+ viewHolder.amountTextView.setText(
+ String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+ viewHolder.iconImageView.setImageResource(R.drawable.gm_amp_24);
+ viewHolder.itemView.setBackgroundResource(
+ R.color.battery_consumer_bg_measured_energy);
+ break;
+ case POWER_CUSTOM:
+ viewHolder.titleTextView.setText(entry.title);
+ viewHolder.amountTextView.setText(
+ String.format(Locale.getDefault(), "%.1f mAh", entry.value));
+ viewHolder.iconImageView.setImageResource(R.drawable.gm_custom_24);
+ viewHolder.itemView.setBackgroundResource(
+ R.color.battery_consumer_bg_measured_energy);
break;
case DURATION:
viewHolder.titleTextView.setText(entry.title);
- viewHolder.amountTextView.setText(
- String.format(Locale.getDefault(), "%,d ms", (long) entry.value));
+ final long durationMs = (long) entry.value;
+ CharSequence text;
+ if (durationMs < MILLIS_IN_MINUTE) {
+ text = String.format(Locale.getDefault(), "%,d ms", durationMs);
+ } else {
+ text = String.format(Locale.getDefault(), "%,d m %d s",
+ durationMs / MILLIS_IN_MINUTE,
+ (durationMs % MILLIS_IN_MINUTE) / 1000);
+ }
+
+ viewHolder.amountTextView.setText(text);
+ viewHolder.iconImageView.setImageResource(R.drawable.gm_timer_24);
+ viewHolder.itemView.setBackground(null);
break;
}
- double proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
- viewHolder.percentTextView.setText(String.format(Locale.getDefault(), "%.1f%%",
- proportion));
+ double proportion;
+ if (entry.isSystemBatteryConsumer) {
+ proportion = entry.value != 0 ? entry.total * 100 / entry.value : 0;
+ } else {
+ proportion = entry.total != 0 ? entry.value * 100 / entry.total : 0;
+ }
+ viewHolder.percentTextView.setText(
+ String.format(Locale.getDefault(), "%.1f%%", proportion));
}
}
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index fb0dd46..b2b9ab3 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -93,8 +93,8 @@
@Test
public void testComputeForPassword_metrics() {
- final PasswordMetrics metrics =
- PasswordMetrics.computeForPassword("6B~0z1Z3*8A".getBytes());
+ final PasswordMetrics metrics = PasswordMetrics.computeForPasswordOrPin(
+ "6B~0z1Z3*8A".getBytes(), /* isPin */ false);
assertEquals(11, metrics.length);
assertEquals(4, metrics.letters);
assertEquals(3, metrics.upperCase);
@@ -133,61 +133,71 @@
@Test
public void testDetermineComplexity_lowNumeric() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPassword("1234".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("1234".getBytes(),
+ /* isPin */true).determineComplexity());
}
@Test
public void testDetermineComplexity_lowNumericComplex() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPassword("124".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("124".getBytes(),
+ /* isPin */ true).determineComplexity());
}
@Test
public void testDetermineComplexity_lowAlphabetic() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPassword("a!".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("a!".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
public void testDetermineComplexity_lowAlphanumeric() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- PasswordMetrics.computeForPassword("a!1".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("a!1".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
public void testDetermineComplexity_mediumNumericComplex() {
assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPassword("1238".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("1238".getBytes(),
+ /* isPin */ true).determineComplexity());
}
@Test
public void testDetermineComplexity_mediumAlphabetic() {
assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPassword("ab!c".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("ab!c".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
public void testDetermineComplexity_mediumAlphanumeric() {
assertEquals(PASSWORD_COMPLEXITY_MEDIUM,
- PasswordMetrics.computeForPassword("ab!1".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("ab!1".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
public void testDetermineComplexity_highNumericComplex() {
assertEquals(PASSWORD_COMPLEXITY_HIGH,
- PasswordMetrics.computeForPassword("12389647!".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("12389647!".getBytes(),
+ /* isPin */ true).determineComplexity());
}
@Test
public void testDetermineComplexity_highAlphabetic() {
assertEquals(PASSWORD_COMPLEXITY_HIGH,
- PasswordMetrics.computeForPassword("alphabetic!".getBytes()).determineComplexity());
+ PasswordMetrics.computeForPasswordOrPin("alphabetic!".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
public void testDetermineComplexity_highAlphanumeric() {
- assertEquals(PASSWORD_COMPLEXITY_HIGH, PasswordMetrics.computeForPassword(
- "alphanumeric123!".getBytes()).determineComplexity());
+ assertEquals(PASSWORD_COMPLEXITY_HIGH,
+ PasswordMetrics.computeForPasswordOrPin("alphanumeric123!".getBytes(),
+ /* isPin */ false).determineComplexity());
}
@Test
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
index e951054..f1be173 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -28,6 +28,7 @@
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
import static org.junit.Assert.assertEquals;
@@ -80,7 +81,7 @@
public void testGetMinMetrics_numeric() {
PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
PasswordMetrics minMetrics = policy.getMinMetrics();
- assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
assertEquals(TEST_VALUE, minMetrics.length);
assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
assertEquals(0, minMetrics.letters);
@@ -104,7 +105,7 @@
public void testGetMinMetrics_numericComplex() {
PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
PasswordMetrics minMetrics = policy.getMinMetrics();
- assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(CREDENTIAL_TYPE_PIN, minMetrics.credType);
assertEquals(TEST_VALUE, minMetrics.length);
assertEquals(0, minMetrics.numeric);
assertEquals(0, minMetrics.letters);
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
index 56c685a..3d18337 100644
--- a/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSessionUnitTest.java
@@ -20,6 +20,7 @@
import static org.testng.Assert.expectThrows;
+import android.app.appsearch.exceptions.AppSearchException;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
@@ -89,8 +90,12 @@
});
// Verify the NullPointException has been thrown.
- ExecutionException executionException = expectThrows(ExecutionException.class,
- putDocumentsFuture::get);
- assertThat(executionException.getCause()).isInstanceOf(NullPointerException.class);
+ ExecutionException executionException =
+ expectThrows(ExecutionException.class, putDocumentsFuture::get);
+ assertThat(executionException.getCause()).isInstanceOf(AppSearchException.class);
+ AppSearchException appSearchException = (AppSearchException) executionException.getCause();
+ assertThat(appSearchException.getResultCode())
+ .isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
+ assertThat(appSearchException.getMessage()).startsWith("NullPointerException");
}
}
diff --git a/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java
new file mode 100644
index 0000000..de0670b
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/external/app/AppSearchResultTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.app.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import org.junit.Test;
+
+public class AppSearchResultTest {
+ @Test
+ public void testMapNullPointerException() {
+ NullPointerException e =
+ expectThrows(
+ NullPointerException.class,
+ () -> {
+ Object o = null;
+ o.toString();
+ });
+ AppSearchResult<?> result = AppSearchResult.throwableToFailedResult(e);
+ assertThat(result.getResultCode()).isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR);
+ // Makes sure the exception name is included in the string. Some exceptions have terse or
+ // missing strings so it's confusing to read the output without the exception name.
+ assertThat(result.getErrorMessage()).startsWith("NullPointerException");
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index 22c71b52..e7b88c8 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -32,6 +32,7 @@
import android.graphics.Rect;
import android.os.Handler;
import android.os.ICancellationSignal;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -189,4 +190,15 @@
verifyNoMoreInteractions(mRemote);
}
+ @Test
+ public void testClose_whileActive() throws RemoteException {
+ mConnection.startCapture(mSurface, mRemote);
+
+ mCallback.completeStartRequest();
+ assertTrue(mConnection.isActive());
+
+ mConnection.close();
+ mCallback.completeEndRequest();
+ assertFalse(mConnection.isActive());
+ }
}
diff --git a/core/tests/coretests/src/android/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java
index 614e7c1..83280f1 100644
--- a/core/tests/coretests/src/android/window/WindowContextTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextTest.java
@@ -17,6 +17,7 @@
package android.window;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -209,6 +210,38 @@
mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY);
}
+ @Test
+ public void testWindowContextAddViewWithSubWindowType_NotCrash() throws Throwable {
+ final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD);
+ final WindowManager wm = windowContext.getSystemService(WindowManager.class);
+
+ // Create a WindowToken with system window type.
+ final IBinder existingToken = new Binder();
+ mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId(),
+ null /* options */);
+
+ final WindowManager.LayoutParams params =
+ new WindowManager.LayoutParams(TYPE_INPUT_METHOD);
+ params.token = existingToken;
+ final View parentWindow = new View(windowContext);
+
+ final AttachStateListener listener = new AttachStateListener();
+ parentWindow.addOnAttachStateChangeListener(listener);
+
+ // Add the parent window
+ mInstrumentation.runOnMainSync(() -> wm.addView(parentWindow, params));
+
+ assertTrue(listener.mLatch.await(4, TimeUnit.SECONDS));
+
+ final WindowManager.LayoutParams subWindowAttrs =
+ new WindowManager.LayoutParams(TYPE_APPLICATION_ATTACHED_DIALOG);
+ subWindowAttrs.token = parentWindow.getWindowToken();
+ final View subWindow = new View(windowContext);
+
+ // Add a window with sub-window type.
+ mInstrumentation.runOnMainSync(() -> wm.addView(subWindow, subWindowAttrs));
+ }
+
private WindowContext createWindowContext() {
return createWindowContext(TYPE_APPLICATION_OVERLAY);
}
@@ -219,4 +252,16 @@
.getDisplay(DEFAULT_DISPLAY);
return (WindowContext) instContext.createWindowContext(display, type, null /* options */);
}
+
+ private static class AttachStateListener implements View.OnAttachStateChangeListener {
+ final CountDownLatch mLatch = new CountDownLatch(1);
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mLatch.countDown();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {}
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
index cf47efd..c63ec45 100644
--- a/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/AmbientDisplayPowerCalculatorTest.java
@@ -65,12 +65,12 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(
SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
.isEqualTo(90 * MINUTE_IN_MS);
// 100,000,00 uC / 1000 (micro-/milli-) / 360 (seconds/hour) = 27.777778 mAh
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(27.777778);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
}
@@ -91,11 +91,11 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(
SystemBatteryConsumer.DRAIN_TYPE_AMBIENT_DISPLAY);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
.isEqualTo(90 * MINUTE_IN_MS);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(15.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
index 0dd1977..b253599 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsTest.java
@@ -77,7 +77,7 @@
.setTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND, 1000)
.setTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND, 2000)
.setConsumedPower(
- BatteryConsumer.POWER_COMPONENT_USAGE, 300)
+ BatteryConsumer.POWER_COMPONENT_SCREEN, 300)
.setConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU, 400)
.setConsumedPowerForCustomComponent(
@@ -123,7 +123,7 @@
assertThat(uidBatteryConsumer.getTimeInStateMs(
UidBatteryConsumer.STATE_BACKGROUND)).isEqualTo(2000);
assertThat(uidBatteryConsumer.getConsumedPower(
- BatteryConsumer.POWER_COMPONENT_USAGE)).isEqualTo(300);
+ BatteryConsumer.POWER_COMPONENT_SCREEN)).isEqualTo(300);
assertThat(uidBatteryConsumer.getConsumedPower(
BatteryConsumer.POWER_COMPONENT_CPU)).isEqualTo(400);
assertThat(uidBatteryConsumer.getConsumedPowerForCustomComponent(
@@ -157,6 +157,8 @@
BatteryConsumer.FIRST_CUSTOM_TIME_COMPONENT_ID)).isEqualTo(10400);
assertThat(systemBatteryConsumer.getConsumedPower()).isEqualTo(20300);
assertThat(systemBatteryConsumer.getPowerConsumedByApps()).isEqualTo(20000);
+ assertThat(systemBatteryConsumer.getUsageDurationMillis())
+ .isEqualTo(10400); // max
assertThat(systemBatteryConsumer.getCustomPowerComponentCount()).isEqualTo(1);
assertThat(systemBatteryConsumer.getCustomPowerComponentName(
BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID)).isEqualTo("FOO");
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
index f65fb95..bf87683 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderLatencyObserverTest.java
@@ -16,6 +16,8 @@
package com.android.internal.os;
+import static com.android.internal.os.BinderLatencyProto.Dims.SYSTEM_SERVER;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -23,16 +25,21 @@
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BinderInternal.CallSession;
import com.android.internal.os.BinderLatencyObserver.LatencyDims;
+import com.android.internal.os.BinderLatencyProto.ApiStats;
+import com.android.internal.os.BinderLatencyProto.Dims;
+import com.android.internal.os.BinderLatencyProto.RepeatedApiStats;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
@@ -49,11 +56,17 @@
CallSession callSession = new CallSession();
callSession.binderClass = binder.getClass();
callSession.transactionCode = 1;
+
+ blo.setElapsedTime(2);
blo.callEnded(callSession);
+ blo.setElapsedTime(4);
blo.callEnded(callSession);
+ blo.setElapsedTime(6);
blo.callEnded(callSession);
callSession.transactionCode = 2;
+ blo.setElapsedTime(8);
blo.callEnded(callSession);
+ blo.setElapsedTime(10);
blo.callEnded(callSession);
ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
@@ -74,8 +87,10 @@
CallSession callSession = new CallSession();
callSession.binderClass = binder.getClass();
callSession.transactionCode = 1;
+ blo.setElapsedTime(2);
blo.callEnded(callSession);
callSession.transactionCode = 2;
+ blo.setElapsedTime(4);
blo.callEnded(callSession);
ArrayMap<LatencyDims, int[]> latencyHistograms = blo.getLatencyHistograms();
@@ -89,13 +104,13 @@
@Test
public void testTooCallLengthOverflow() {
TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
- blo.setElapsedTime(2L + (long) Integer.MAX_VALUE);
blo.setHistogramBucketsParams(5, 5, 1.125f);
Binder binder = new Binder();
CallSession callSession = new CallSession();
callSession.binderClass = binder.getClass();
callSession.transactionCode = 1;
+ blo.setElapsedTime(2L + (long) Integer.MAX_VALUE);
blo.callEnded(callSession);
// The long call should be capped to maxint (to not overflow) and placed in the last bucket.
@@ -114,6 +129,7 @@
CallSession callSession = new CallSession();
callSession.binderClass = binder.getClass();
callSession.transactionCode = 1;
+ blo.setElapsedTime(2);
blo.callEnded(callSession);
LatencyDims dims = new LatencyDims(binder.getClass(), 1);
@@ -122,14 +138,111 @@
assertThat(blo.getLatencyHistograms().get(dims))
.asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
// Try to add another sample.
+ blo.setElapsedTime(2);
blo.callEnded(callSession);
// Make sure the buckets don't overflow.
assertThat(blo.getLatencyHistograms().get(dims))
.asList().containsExactly(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
+ @Test
+ public void testSingleAtomPush() {
+ TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+ Binder binder = new Binder();
+ CallSession callSession = new CallSession();
+ callSession.binderClass = binder.getClass();
+ callSession.transactionCode = 1;
+ blo.setElapsedTime(7);
+ blo.callEnded(callSession);
+ blo.callEnded(callSession);
+ blo.setElapsedTime(8);
+ blo.callEnded(callSession);
+
+ // Trigger the statsd push.
+ blo.getStatsdPushRunnable().run();
+
+ ProtoOutputStream expectedProto = new ProtoOutputStream();
+ long apiStatsToken = expectedProto.start(RepeatedApiStats.API_STATS);
+ long dimsToken = expectedProto.start(ApiStats.DIMS);
+ expectedProto.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+ expectedProto.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+ expectedProto.write(Dims.SERVICE_METHOD_NAME, "1");
+ expectedProto.end(dimsToken);
+ expectedProto.write(ApiStats.FIRST_BUCKET_INDEX, 3);
+ expectedProto.write(ApiStats.BUCKETS, 2);
+ expectedProto.write(ApiStats.BUCKETS, 1);
+ expectedProto.end(apiStatsToken);
+
+ assertThat(blo.getWrittenAtoms())
+ .containsExactly(Arrays.toString(expectedProto.getBytes()));
+ }
+
+ @Test
+ public void testMultipleAtomPush() {
+ TestBinderLatencyObserver blo = new TestBinderLatencyObserver();
+
+ BinderTransactionNameResolver resolver = new BinderTransactionNameResolver();
+
+
+ Binder binder = new Binder();
+ CallSession callSession = new CallSession();
+ callSession.binderClass = binder.getClass();
+ callSession.transactionCode = 1;
+ blo.setElapsedTime(1);
+ blo.callEnded(callSession);
+ callSession.transactionCode = 2;
+ blo.setElapsedTime(5);
+ blo.callEnded(callSession);
+ callSession.transactionCode = 3;
+ blo.callEnded(callSession);
+
+ // Trigger the statsd push.
+ blo.getStatsdPushRunnable().run();
+
+ ProtoOutputStream expectedProto1 = new ProtoOutputStream();
+ long apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+ long dimsToken = expectedProto1.start(ApiStats.DIMS);
+ expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+ expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+ expectedProto1.write(Dims.SERVICE_METHOD_NAME, "1");
+ expectedProto1.end(dimsToken);
+ expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 0);
+ expectedProto1.write(ApiStats.BUCKETS, 1);
+ expectedProto1.end(apiStatsToken);
+
+ apiStatsToken = expectedProto1.start(RepeatedApiStats.API_STATS);
+ dimsToken = expectedProto1.start(ApiStats.DIMS);
+ expectedProto1.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+ expectedProto1.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+ expectedProto1.write(Dims.SERVICE_METHOD_NAME, "2");
+ expectedProto1.end(dimsToken);
+ expectedProto1.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+ expectedProto1.write(ApiStats.BUCKETS, 1);
+ expectedProto1.end(apiStatsToken);
+
+ ProtoOutputStream expectedProto2 = new ProtoOutputStream();
+ apiStatsToken = expectedProto2.start(RepeatedApiStats.API_STATS);
+ dimsToken = expectedProto2.start(ApiStats.DIMS);
+ expectedProto2.write(Dims.PROCESS_SOURCE, SYSTEM_SERVER);
+ expectedProto2.write(Dims.SERVICE_CLASS_NAME, binder.getClass().getName());
+ expectedProto2.write(Dims.SERVICE_METHOD_NAME, "3");
+ expectedProto2.end(dimsToken);
+ expectedProto2.write(ApiStats.FIRST_BUCKET_INDEX, 1);
+ expectedProto2.write(ApiStats.BUCKETS, 1);
+ expectedProto2.end(apiStatsToken);
+
+ // Each ApiStats is around ~60 bytes so only two should fit in an atom.
+ assertThat(blo.getWrittenAtoms())
+ .containsExactly(
+ Arrays.toString(expectedProto1.getBytes()),
+ Arrays.toString(expectedProto2.getBytes()))
+ .inOrder();
+ }
+
public static class TestBinderLatencyObserver extends BinderLatencyObserver {
private long mElapsedTime = 0;
+ private ArrayList<String> mWrittenAtoms;
TestBinderLatencyObserver() {
// Make random generator not random.
@@ -145,16 +258,30 @@
}
});
setSamplingInterval(1);
+ mWrittenAtoms = new ArrayList<>();
}
@Override
protected long getElapsedRealtimeMicro() {
- mElapsedTime += 2;
return mElapsedTime;
}
+ @Override
+ protected int getMaxAtomSizeBytes() {
+ return 1100;
+ }
+
+ @Override
+ protected void writeAtomToStatsd(ProtoOutputStream atom) {
+ mWrittenAtoms.add(Arrays.toString(atom.getBytes()));
+ }
+
public void setElapsedTime(long time) {
mElapsedTime = time;
}
+
+ public ArrayList<String> getWrittenAtoms() {
+ return mWrittenAtoms;
+ }
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
index 781e725..a9800b7 100644
--- a/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/IdlePowerCalculatorTest.java
@@ -48,9 +48,9 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_IDLE);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_IDLE))
.isEqualTo(3000);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_IDLE))
.isWithin(PRECISION).of(0.7);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
index 8f21503..71dbcdb 100644
--- a/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MemoryPowerCalculatorTest.java
@@ -55,9 +55,9 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_MEMORY);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_MEMORY))
.isEqualTo(3000);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MEMORY))
.isWithin(PRECISION).of(0.7);
}
}
diff --git a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
index 9cd6ea8..7d829e4 100644
--- a/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/ScreenPowerCalculatorTest.java
@@ -84,13 +84,13 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
// 600000000 uAs * (1 mA / 1000 uA) * (1 h / 3600 s) = 166.66666 mAh
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(166.66666);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
assertThat(consumer.getConsumedPower())
.isWithin(PRECISION).of(166.66666);
@@ -153,11 +153,11 @@
SystemBatteryConsumer consumer =
mStatsRule.getSystemBatteryConsumer(SystemBatteryConsumer.DRAIN_TYPE_SCREEN);
- assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_USAGE))
+ assertThat(consumer.getUsageDurationMillis(BatteryConsumer.TIME_COMPONENT_SCREEN))
.isEqualTo(80 * MINUTE_IN_MS);
- assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isWithin(PRECISION).of(92.0);
- assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_USAGE))
+ assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
.isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
assertThat(consumer.getConsumedPower())
.isWithin(PRECISION).of(92.0);
diff --git a/core/tests/nfctests/Android.bp b/core/tests/nfctests/Android.bp
new file mode 100644
index 0000000..335cea1
--- /dev/null
+++ b/core/tests/nfctests/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ // 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_test {
+ name: "NfcManagerTests",
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ ],
+ libs: [
+ "android.test.runner",
+ ],
+ srcs: ["src/**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+}
diff --git a/core/tests/nfctests/AndroidManifest.xml b/core/tests/nfctests/AndroidManifest.xml
new file mode 100644
index 0000000..99e2c34c
--- /dev/null
+++ b/core/tests/nfctests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.nfc">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <!-- This is a self-instrumenting test package. -->
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.nfc"
+ android:label="NFC Manager Tests">
+ </instrumentation>
+
+</manifest>
+
diff --git a/core/tests/nfctests/AndroidTest.xml b/core/tests/nfctests/AndroidTest.xml
new file mode 100644
index 0000000..490d6f5
--- /dev/null
+++ b/core/tests/nfctests/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for NFC Manager test cases">
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-suite-tag" value="apct-instrumentation"/>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="NfcManagerTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct"/>
+ <option name="test-tag" value="NfcManagerTests"/>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.nfc" />
+ <option name="hidden-api-checks" value="false"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/core/tests/nfctests/OWNERS b/core/tests/nfctests/OWNERS
new file mode 100644
index 0000000..34b095c
--- /dev/null
+++ b/core/tests/nfctests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/nfc/OWNERS
diff --git a/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
new file mode 100644
index 0000000..43f9b6f
--- /dev/null
+++ b/core/tests/nfctests/src/android/nfc/NfcControllerAlwaysOnListenerTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.nfc;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.nfc.NfcAdapter.ControllerAlwaysOnListener;
+import android.os.RemoteException;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Test of {@link NfcControllerAlwaysOnListener}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NfcControllerAlwaysOnListenerTest {
+
+ private INfcAdapter mNfcAdapter = mock(INfcAdapter.class);
+
+ private Throwable mThrowRemoteException = new RemoteException("RemoteException");
+
+ private static Executor getExecutor() {
+ return new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ command.run();
+ }
+ };
+ }
+
+ private static void verifyListenerInvoked(ControllerAlwaysOnListener listener) {
+ verify(listener, times(1)).onControllerAlwaysOnChanged(anyBoolean());
+ }
+
+ @Test
+ public void testRegister_RegisterUnregister() throws RemoteException {
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+ ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+ // Verify that the state listener registered with the NFC Adapter
+ mListener.register(getExecutor(), mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Register a second client and no new call to NFC Adapter
+ mListener.register(getExecutor(), mockListener2);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Unregister first listener
+ mListener.unregister(mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(0)).unregisterControllerAlwaysOnListener(any());
+
+ // Unregister second listener and the state listener registered with the NFC Adapter
+ mListener.unregister(mockListener2);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+ verify(mNfcAdapter, times(1)).unregisterControllerAlwaysOnListener(any());
+ }
+
+ @Test
+ public void testRegister_FirstRegisterFails() throws RemoteException {
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener1 = mock(ControllerAlwaysOnListener.class);
+ ControllerAlwaysOnListener mockListener2 = mock(ControllerAlwaysOnListener.class);
+
+ // Throw a remote exception whenever first registering
+ doThrow(mThrowRemoteException).when(mNfcAdapter).registerControllerAlwaysOnListener(
+ any());
+
+ mListener.register(getExecutor(), mockListener1);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // No longer throw an exception, instead succeed
+ doNothing().when(mNfcAdapter).registerControllerAlwaysOnListener(any());
+
+ // Register a different listener
+ mListener.register(getExecutor(), mockListener2);
+ verify(mNfcAdapter, times(2)).registerControllerAlwaysOnListener(any());
+
+ // Ensure first and second listener were invoked
+ mListener.onControllerAlwaysOnChanged(true);
+ verifyListenerInvoked(mockListener1);
+ verifyListenerInvoked(mockListener2);
+ }
+
+ @Test
+ public void testRegister_RegisterSameListenerTwice() throws RemoteException {
+ NfcControllerAlwaysOnListener mListener =
+ new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+
+ // Register the same listener Twice
+ mListener.register(getExecutor(), mockListener);
+ mListener.register(getExecutor(), mockListener);
+ verify(mNfcAdapter, times(1)).registerControllerAlwaysOnListener(any());
+
+ // Invoke a state change and ensure the listener is only called once
+ mListener.onControllerAlwaysOnChanged(true);
+ verifyListenerInvoked(mockListener);
+ }
+
+ @Test
+ public void testNotify_AllListenersNotified() throws RemoteException {
+
+ NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+ List<ControllerAlwaysOnListener> mockListeners = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+ listener.register(getExecutor(), mockListener);
+ mockListeners.add(mockListener);
+ }
+
+ // Invoke a state change and ensure all listeners are invoked
+ listener.onControllerAlwaysOnChanged(true);
+ for (ControllerAlwaysOnListener mListener : mockListeners) {
+ verifyListenerInvoked(mListener);
+ }
+ }
+
+ @Test
+ public void testStateChange_CorrectValue() {
+ runStateChangeValue(true, true);
+ runStateChangeValue(false, false);
+
+ }
+
+ private void runStateChangeValue(boolean isEnabledIn, boolean isEnabledOut) {
+ NfcControllerAlwaysOnListener listener = new NfcControllerAlwaysOnListener(mNfcAdapter);
+ ControllerAlwaysOnListener mockListener = mock(ControllerAlwaysOnListener.class);
+ listener.register(getExecutor(), mockListener);
+ listener.onControllerAlwaysOnChanged(isEnabledIn);
+ verify(mockListener, times(1)).onControllerAlwaysOnChanged(isEnabledOut);
+ verify(mockListener, times(0)).onControllerAlwaysOnChanged(!isEnabledOut);
+ }
+}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 2fe8b28..5f34426 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -348,6 +348,8 @@
<permission name="android.permission.INSTALL_LOCATION_PROVIDER"/>
<permission name="android.permission.INSTALL_PACKAGES"/>
<!-- Needed for test only -->
+ <permission name="android.permission.ACCESS_MTP"/>
+ <!-- Needed for test only -->
<permission name="android.permission.INTERACT_ACROSS_PROFILES"/>
<!-- Permission required to test onPermissionsChangedListener -->
<permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS"/>
@@ -404,6 +406,7 @@
<permission name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
+ <permission name="android.permission.UWB_PRIVILEGED"/>
<permission name="android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"/>
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
<permission name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
diff --git a/errorprone/java/android/annotation/SuppressLint.java b/errorprone/java/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/errorprone/java/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ /**
+ * The set of warnings (identified by the lint issue id) that should be
+ * ignored by lint. It is not an error to specify an unrecognized name.
+ */
+ String[] value();
+}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
new file mode 100644
index 0000000..3b5a58c
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/RequiresPermissionChecker.java
@@ -0,0 +1,328 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+import static com.google.errorprone.matchers.Matchers.allOf;
+import static com.google.errorprone.matchers.Matchers.anyOf;
+import static com.google.errorprone.matchers.Matchers.enclosingClass;
+import static com.google.errorprone.matchers.Matchers.instanceMethod;
+import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
+import static com.google.errorprone.matchers.Matchers.methodInvocation;
+import static com.google.errorprone.matchers.Matchers.methodIsNamed;
+import static com.google.errorprone.matchers.Matchers.staticMethod;
+
+import android.annotation.SuppressLint;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.MethodTree;
+import com.sun.source.util.TreeScanner;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.ClassSymbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Type;
+import com.sun.tools.javac.code.Type.ClassType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.AnnotationValue;
+import javax.lang.model.element.ExecutableElement;
+
+/**
+ * Inspects both the client and server side of AIDL interfaces to ensure that
+ * any {@code RequiresPermission} annotations are consistently declared and
+ * enforced.
+ */
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "AndroidFrameworkRequiresPermission",
+ summary = "Verifies that @RequiresPermission annotations are consistent across AIDL",
+ severity = WARNING)
+public final class RequiresPermissionChecker extends BugChecker implements MethodTreeMatcher {
+ private static final String ANNOTATION_REQUIRES_PERMISSION = "RequiresPermission";
+
+ private static final Matcher<ExpressionTree> ENFORCE_VIA_CONTEXT = methodInvocation(
+ instanceMethod()
+ .onDescendantOf("android.content.Context")
+ .withNameMatching(
+ Pattern.compile("^(enforce|check)(Calling)?(OrSelf)?Permission$")));
+ private static final Matcher<ExpressionTree> ENFORCE_VIA_CHECKER = methodInvocation(
+ staticMethod()
+ .onClass("android.content.PermissionChecker")
+ .withNameMatching(Pattern.compile("^check.*")));
+
+ private static final Matcher<MethodTree> BINDER_INTERNALS = allOf(
+ enclosingClass(isSubtypeOf("android.os.IInterface")),
+ anyOf(
+ methodIsNamed("onTransact"),
+ methodIsNamed("dump"),
+ enclosingClass(simpleNameMatches(Pattern.compile("^(Stub|Default|Proxy)$")))));
+ private static final Matcher<MethodTree> LOCAL_INTERNALS = anyOf(
+ methodIsNamed("finalize"),
+ allOf(
+ enclosingClass(isSubtypeOf("android.content.BroadcastReceiver")),
+ methodIsNamed("onReceive")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.database.ContentObserver")),
+ methodIsNamed("onChange")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.os.Handler")),
+ methodIsNamed("handleMessage")),
+ allOf(
+ enclosingClass(isSubtypeOf("android.os.IBinder.DeathRecipient")),
+ methodIsNamed("binderDied")));
+
+ private static final Matcher<ExpressionTree> CLEAR_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("clearCallingIdentity()"));
+ private static final Matcher<ExpressionTree> RESTORE_CALL = methodInvocation(staticMethod()
+ .onClass("android.os.Binder").withSignature("restoreCallingIdentity(long)"));
+
+ @Override
+ public Description matchMethod(MethodTree tree, VisitorState state) {
+ // Ignore methods without an implementation
+ if (tree.getBody() == null) return Description.NO_MATCH;
+
+ // Ignore certain types of Binder generated code
+ if (BINDER_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+ // Ignore known-local methods which don't need to propagate
+ if (LOCAL_INTERNALS.matches(tree, state)) return Description.NO_MATCH;
+
+ // Ignore when suppressed via superclass
+ final MethodSymbol method = ASTHelpers.getSymbol(tree);
+ if (isSuppressedRecursively(method, state)) return Description.NO_MATCH;
+
+ // First, look at all outgoing method invocations to ensure that we
+ // carry those annotations forward; yell if we're too narrow
+ final ParsedRequiresPermission expectedPerm = parseRequiresPermissionRecursively(
+ method, state);
+ final ParsedRequiresPermission actualPerm = new ParsedRequiresPermission();
+ final Description desc = tree.accept(new TreeScanner<Description, Void>() {
+ private boolean clearedCallingIdentity = false;
+
+ @Override
+ public Description visitMethodInvocation(MethodInvocationTree node, Void param) {
+ if (CLEAR_CALL.matches(node, state)) {
+ clearedCallingIdentity = true;
+ } else if (RESTORE_CALL.matches(node, state)) {
+ clearedCallingIdentity = false;
+ } else if (!clearedCallingIdentity) {
+ final ParsedRequiresPermission nodePerm = parseRequiresPermissionRecursively(
+ node, state);
+ if (!expectedPerm.containsAll(nodePerm)) {
+ return buildDescription(node).setMessage("Annotated " + expectedPerm
+ + " but too narrow; invokes method requiring " + nodePerm).build();
+ } else {
+ actualPerm.addAll(nodePerm);
+ }
+ }
+ return super.visitMethodInvocation(node, param);
+ }
+
+ @Override
+ public Description reduce(Description r1, Description r2) {
+ return (r1 != null) ? r1 : r2;
+ }
+ }, null);
+ if (desc != null) return desc;
+
+ // Second, determine if we actually used all permissions that we claim
+ // to require; yell if we're too broad
+ if (!actualPerm.containsAll(expectedPerm)) {
+ return buildDescription(tree).setMessage("Annotated " + expectedPerm
+ + " but too wide; only invokes methods requiring " + actualPerm).build();
+ }
+
+ return Description.NO_MATCH;
+ }
+
+ static class ParsedRequiresPermission {
+ final Set<String> allOf = new HashSet<>();
+ final Set<String> anyOf = new HashSet<>();
+
+ public boolean isEmpty() {
+ return allOf.isEmpty() && anyOf.isEmpty();
+ }
+
+ /**
+ * Validate that this annotation effectively "contains" the given
+ * annotation. This is typically used to ensure that a method carries
+ * along all relevant annotations for the methods it invokes.
+ */
+ public boolean containsAll(ParsedRequiresPermission perm) {
+ boolean allMet = allOf.containsAll(perm.allOf);
+ boolean anyMet = false;
+ if (perm.anyOf.isEmpty()) {
+ anyMet = true;
+ } else {
+ for (String anyPerm : perm.anyOf) {
+ if (allOf.contains(anyPerm) || anyOf.contains(anyPerm)) {
+ anyMet = true;
+ }
+ }
+ }
+ return allMet && anyMet;
+ }
+
+ @Override
+ public String toString() {
+ if (isEmpty()) {
+ return "[none]";
+ }
+ String res = "{allOf=" + allOf;
+ if (!anyOf.isEmpty()) {
+ res += " anyOf=" + anyOf;
+ }
+ res += "}";
+ return res;
+ }
+
+ public void addAll(ParsedRequiresPermission perm) {
+ this.allOf.addAll(perm.allOf);
+ this.anyOf.addAll(perm.anyOf);
+ }
+
+ public void addAll(AnnotationMirror a) {
+ for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : a
+ .getElementValues().entrySet()) {
+ if (entry.getKey().getSimpleName().contentEquals("value")) {
+ maybeAdd(allOf, entry.getValue());
+ } else if (entry.getKey().getSimpleName().contentEquals("allOf")) {
+ maybeAdd(allOf, entry.getValue());
+ } else if (entry.getKey().getSimpleName().contentEquals("anyOf")) {
+ maybeAdd(anyOf, entry.getValue());
+ }
+ }
+ }
+
+ private static void maybeAdd(Set<String> set, Object value) {
+ if (value instanceof AnnotationValue) {
+ maybeAdd(set, ((AnnotationValue) value).getValue());
+ } else if (value instanceof String) {
+ set.add((String) value);
+ } else if (value instanceof Collection) {
+ for (Object o : (Collection) value) {
+ maybeAdd(set, o);
+ }
+ } else {
+ throw new RuntimeException(String.valueOf(value.getClass()));
+ }
+ }
+ }
+
+ private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+ MethodInvocationTree tree, VisitorState state) {
+ if (ENFORCE_VIA_CONTEXT.matches(tree, state)) {
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(0))));
+ return res;
+ } else if (ENFORCE_VIA_CHECKER.matches(tree, state)) {
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.add(String.valueOf(ASTHelpers.constValue(tree.getArguments().get(1))));
+ return res;
+ } else {
+ final MethodSymbol method = ASTHelpers.getSymbol(tree);
+ return parseRequiresPermissionRecursively(method, state);
+ }
+ }
+
+ /**
+ * Parse any {@code RequiresPermission} annotations associated with the
+ * given method, defined either directly on the method or by any superclass.
+ */
+ private static ParsedRequiresPermission parseRequiresPermissionRecursively(
+ MethodSymbol method, VisitorState state) {
+ final List<MethodSymbol> symbols = new ArrayList<>();
+ symbols.add(method);
+ symbols.addAll(ASTHelpers.findSuperMethods(method, state.getTypes()));
+
+ final ParsedRequiresPermission res = new ParsedRequiresPermission();
+ for (MethodSymbol symbol : symbols) {
+ for (AnnotationMirror a : symbol.getAnnotationMirrors()) {
+ if (a.getAnnotationType().asElement().getSimpleName()
+ .contentEquals(ANNOTATION_REQUIRES_PERMISSION)) {
+ res.addAll(a);
+ }
+ }
+ }
+ return res;
+ }
+
+ private boolean isSuppressedRecursively(MethodSymbol method, VisitorState state) {
+ // Is method suppressed anywhere?
+ if (isSuppressed(method)) return true;
+ for (MethodSymbol symbol : ASTHelpers.findSuperMethods(method, state.getTypes())) {
+ if (isSuppressed(symbol)) return true;
+ }
+
+ // Is class suppressed anywhere?
+ final ClassSymbol clazz = ASTHelpers.enclosingClass(method);
+ if (isSuppressed(clazz)) return true;
+ Type type = clazz.getSuperclass();
+ while (type != null) {
+ if (isSuppressed(type.tsym)) return true;
+ if (type instanceof ClassType) {
+ type = ((ClassType) type).supertype_field;
+ } else {
+ type = null;
+ }
+ }
+ return false;
+ }
+
+ public boolean isSuppressed(Symbol symbol) {
+ return isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressWarnings.class))
+ || isSuppressed(ASTHelpers.getAnnotation(symbol, SuppressLint.class));
+ }
+
+ private boolean isSuppressed(SuppressWarnings anno) {
+ return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+ }
+
+ private boolean isSuppressed(SuppressLint anno) {
+ return (anno != null) && !Collections.disjoint(Arrays.asList(anno.value()), allNames());
+ }
+
+ private static Matcher<ClassTree> simpleNameMatches(Pattern pattern) {
+ return new Matcher<ClassTree>() {
+ @Override
+ public boolean matches(ClassTree tree, VisitorState state) {
+ final CharSequence name = tree.getSimpleName().toString();
+ return pattern.matcher(name).matches();
+ }
+ };
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
new file mode 100644
index 0000000..771258d
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/RequiresPermissionCheckerTest.java
@@ -0,0 +1,325 @@
+/*
+ * 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.google.errorprone.bugpatterns.android;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.errorprone.CompilationTestHelper;
+import com.google.errorprone.bugpatterns.android.RequiresPermissionChecker.ParsedRequiresPermission;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(JUnit4.class)
+public class RequiresPermissionCheckerTest {
+ private CompilationTestHelper compilationHelper;
+
+ private static final String RED = "red";
+ private static final String BLUE = "blue";
+
+ @Before
+ public void setUp() {
+ compilationHelper = CompilationTestHelper.newInstance(
+ RequiresPermissionChecker.class, getClass());
+ }
+
+ private static ParsedRequiresPermission build(Collection<String> allOf,
+ Collection<String> anyOf) {
+ ParsedRequiresPermission res = new ParsedRequiresPermission();
+ res.allOf.addAll(allOf);
+ res.anyOf.addAll(anyOf);
+ return res;
+ }
+
+ @Test
+ public void testParser_AllOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+ final ParsedRequiresPermission b = build(Arrays.asList(RED), Arrays.asList());
+ assertTrue(a.containsAll(b));
+ assertFalse(b.containsAll(a));
+ }
+
+ @Test
+ public void testParser_AnyOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(), Arrays.asList(RED, BLUE));
+ final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+ assertTrue(a.containsAll(b));
+ assertTrue(b.containsAll(a));
+ }
+
+ @Test
+ public void testParser_AnyOf_AllOf() {
+ final ParsedRequiresPermission a = build(Arrays.asList(RED, BLUE), Arrays.asList());
+ final ParsedRequiresPermission b = build(Arrays.asList(), Arrays.asList(RED));
+ assertTrue(a.containsAll(b));
+ assertFalse(b.containsAll(a));
+ }
+
+ @Test
+ public void testSimple() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.Context;",
+ "public abstract class ColorManager extends Context {",
+ " private static final String RED = \"red\";",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " @RequiresPermission(allOf={RED, BLUE}) abstract int all();",
+ " @RequiresPermission(anyOf={RED, BLUE}) abstract int any();",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " int redPlusBlue() { return red() + blue(); }",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " int allPlusRed() { return all() + red(); }",
+ " @RequiresPermission(allOf={RED})",
+ " int anyPlusRed() { return any() + red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testManager() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/foo/IColorService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.foo.IColorService;",
+ "public class ColorManager {",
+ " IColorService mService;",
+ " @RequiresPermission(IColorService.RED)",
+ " void redValid() {",
+ " mService.red();",
+ " }",
+ " @RequiresPermission(allOf={IColorService.RED, IColorService.BLUE})",
+ " // BUG: Diagnostic contains:",
+ " void redOverbroad() {",
+ " mService.red();",
+ " }",
+ " @RequiresPermission(IColorService.BLUE)",
+ " void redInvalid() {",
+ " // BUG: Diagnostic contains:",
+ " mService.red();",
+ " }",
+ " void redMissing() {",
+ " // BUG: Diagnostic contains:",
+ " mService.red();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testService() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/foo/IColorService.java")
+ .addSourceFile("/android/os/IInterface.java")
+ .addSourceLines("ColorService.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.Context;",
+ "import android.foo.IColorService;",
+ "class ColorService extends Context implements IColorService {",
+ " public void none() {}",
+ " // BUG: Diagnostic contains:",
+ " public void red() {}",
+ " // BUG: Diagnostic contains:",
+ " public void redAndBlue() {}",
+ " // BUG: Diagnostic contains:",
+ " public void redOrBlue() {}",
+ " void onTransact(int code) {",
+ " red();",
+ " }",
+ "}",
+ "class ValidService extends ColorService {",
+ " public void red() {",
+ " ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+ " }",
+ "}",
+ "class InvalidService extends ColorService {",
+ " public void red() {",
+ " // BUG: Diagnostic contains:",
+ " ((Context) this).enforceCallingOrSelfPermission(BLUE, null);",
+ " }",
+ "}",
+ "class NestedService extends ColorService {",
+ " public void red() {",
+ " enforceRed();",
+ " }",
+ " @RequiresPermission(RED)",
+ " public void enforceRed() {",
+ " ((Context) this).enforceCallingOrSelfPermission(RED, null);",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testBroadcastReceiver() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/content/BroadcastReceiver.java")
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/Intent.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.content.BroadcastReceiver;",
+ "import android.content.Context;",
+ "import android.content.Intent;",
+ "public abstract class ColorManager extends BroadcastReceiver {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void onSend() { red(); }",
+ " public void onReceive(Context context, Intent intent) { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ @Ignore
+ public void testContentObserver() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/database/ContentObserver.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.database.ContentObserver;",
+ "public abstract class ColorManager {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " public void example() {",
+ " ContentObserver ob = new ContentObserver() {",
+ " public void onChange(boolean selfChange) {",
+ " red();",
+ " }",
+ " };",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testHandler() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/Handler.java")
+ .addSourceFile("/android/os/Message.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.Handler;",
+ "import android.os.Message;",
+ "public abstract class ColorManager extends Handler {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void sendMessage() { red(); }",
+ " public void handleMessage(Message msg) { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testDeathRecipient() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/IBinder.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.IBinder;",
+ "public abstract class ColorManager implements IBinder.DeathRecipient {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " // BUG: Diagnostic contains:",
+ " public void binderAlive() { red(); }",
+ " public void binderDied() { red(); }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testClearCallingIdentity() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/os/Binder.java")
+ .addSourceLines("ColorManager.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.os.Binder;",
+ "public abstract class ColorManager {",
+ " private static final String RED = \"red\";",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(RED) abstract int red();",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " @RequiresPermission(BLUE)",
+ " public void half() {",
+ " final long token = Binder.clearCallingIdentity();",
+ " try {",
+ " red();",
+ " } finally {",
+ " Binder.restoreCallingIdentity(token);",
+ " }",
+ " blue();",
+ " }",
+ " public void full() {",
+ " final long token = Binder.clearCallingIdentity();",
+ " red();",
+ " blue();",
+ " }",
+ " @RequiresPermission(allOf={RED, BLUE})",
+ " public void none() {",
+ " red();",
+ " blue();",
+ " final long token = Binder.clearCallingIdentity();",
+ " }",
+ "}")
+ .doTest();
+ }
+
+ @Test
+ public void testSuppressLint() {
+ compilationHelper
+ .addSourceFile("/android/annotation/RequiresPermission.java")
+ .addSourceFile("/android/annotation/SuppressLint.java")
+ .addSourceLines("Example.java",
+ "import android.annotation.RequiresPermission;",
+ "import android.annotation.SuppressLint;",
+ "@SuppressLint(\"AndroidFrameworkRequiresPermission\")",
+ "abstract class Parent {",
+ " private static final String RED = \"red\";",
+ " @RequiresPermission(RED) abstract int red();",
+ "}",
+ "abstract class Child extends Parent {",
+ " private static final String BLUE = \"blue\";",
+ " @RequiresPermission(BLUE) abstract int blue();",
+ " public void toParent() { red(); }",
+ " public void toSibling() { blue(); }",
+ "}")
+ .doTest();
+ }
+}
diff --git a/errorprone/tests/res/android/annotation/RequiresPermission.java b/errorprone/tests/res/android/annotation/RequiresPermission.java
new file mode 100644
index 0000000..670eb3b
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/RequiresPermission.java
@@ -0,0 +1,35 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+ String value() default "";
+ String[] allOf() default {};
+ String[] anyOf() default {};
+}
diff --git a/errorprone/tests/res/android/annotation/SuppressLint.java b/errorprone/tests/res/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..4150c47
--- /dev/null
+++ b/errorprone/tests/res/android/annotation/SuppressLint.java
@@ -0,0 +1,34 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ String[] value();
+}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/content/BroadcastReceiver.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/content/BroadcastReceiver.java
index 1e4fdd7..9d066b7 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/content/BroadcastReceiver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package android.nfc;
+package android.content;
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
- /**
- * Called whenever the controller always on state changes
- *
- * @param isEnabled true if the state is enabled, false otherwise
- */
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class BroadcastReceiver {
+ public void onReceive(Context context, Intent intent) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/errorprone/tests/res/android/content/Context.java b/errorprone/tests/res/android/content/Context.java
index 7ba3fbb..323b8dd 100644
--- a/errorprone/tests/res/android/content/Context.java
+++ b/errorprone/tests/res/android/content/Context.java
@@ -20,4 +20,7 @@
public int getUserId() {
return 0;
}
+
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ }
}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/database/ContentObserver.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/database/ContentObserver.java
index 1e4fdd7..4c73a10 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/database/ContentObserver.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package android.nfc;
+package android.database;
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
- /**
- * Called whenever the controller always on state changes
- *
- * @param isEnabled true if the state is enabled, false otherwise
- */
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public abstract class ContentObserver {
+ public void onChange(boolean selfChange) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/errorprone/tests/res/android/foo/IColorService.java b/errorprone/tests/res/android/foo/IColorService.java
new file mode 100644
index 0000000..20c8e95
--- /dev/null
+++ b/errorprone/tests/res/android/foo/IColorService.java
@@ -0,0 +1,32 @@
+/*
+ * 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.foo;
+
+import android.annotation.RequiresPermission;
+
+public interface IColorService extends android.os.IInterface {
+ public static final String RED = "red";
+ public static final String BLUE = "blue";
+
+ public void none();
+ @RequiresPermission(RED)
+ public void red();
+ @RequiresPermission(allOf = { RED, BLUE })
+ public void redAndBlue();
+ @RequiresPermission(anyOf = { RED, BLUE })
+ public void redOrBlue();
+}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/Handler.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/Handler.java
index 1e4fdd7..f001896 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/Handler.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package android.nfc;
+package android.os;
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
- /**
- * Called whenever the controller always on state changes
- *
- * @param isEnabled true if the state is enabled, false otherwise
- */
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class Handler {
+ public void handleMessage(Message msg) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/IBinder.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/IBinder.java
index 1e4fdd7..214a396 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/IBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-package android.nfc;
+package android.os;
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
- /**
- * Called whenever the controller always on state changes
- *
- * @param isEnabled true if the state is enabled, false otherwise
- */
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public interface IBinder {
+ public interface DeathRecipient {
+ public void binderDied();
+ }
}
diff --git a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl b/errorprone/tests/res/android/os/Message.java
similarity index 61%
copy from core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
copy to errorprone/tests/res/android/os/Message.java
index 1e4fdd7..2421263 100644
--- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl
+++ b/errorprone/tests/res/android/os/Message.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Android Open Source Project
+ * 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.
@@ -14,16 +14,7 @@
* limitations under the License.
*/
-package android.nfc;
+package android.os;
-/**
- * @hide
- */
-oneway interface INfcControllerAlwaysOnStateCallback {
- /**
- * Called whenever the controller always on state changes
- *
- * @param isEnabled true if the state is enabled, false otherwise
- */
- void onControllerAlwaysOnStateChanged(boolean isEnabled);
+public class Message {
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 11c1464..dca5985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -739,14 +739,11 @@
return (isSummary && isSuppressedSummary) || isSuppressedBubble;
}
- private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
- Executor callbackExecutor) {
+ private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) {
if (mBubbleData.isSummarySuppressed(groupKey)) {
mBubbleData.removeSuppressedSummary(groupKey);
if (callback != null) {
- callbackExecutor.execute(() -> {
- callback.accept(mBubbleData.getSummaryKey(groupKey));
- });
+ callback.accept(mBubbleData.getSummaryKey(groupKey));
}
}
}
@@ -1298,8 +1295,10 @@
public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback,
Executor callbackExecutor) {
mMainExecutor.execute(() -> {
- BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, callback,
- callbackExecutor);
+ Consumer<String> cb = callback != null
+ ? (key) -> callbackExecutor.execute(() -> callback.accept(key))
+ : null;
+ BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb);
});
}
@@ -1340,10 +1339,13 @@
@Override
public boolean handleDismissalInterception(BubbleEntry entry,
- @Nullable List<BubbleEntry> children, IntConsumer removeCallback) {
+ @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
+ Executor callbackExecutor) {
+ IntConsumer cb = removeCallback != null
+ ? (index) -> callbackExecutor.execute(() -> removeCallback.accept(index))
+ : null;
return mMainExecutor.executeBlockingForResult(() -> {
- return BubbleController.this.handleDismissalInterception(entry, children,
- removeCallback);
+ return BubbleController.this.handleDismissalInterception(entry, children, cb);
}, Boolean.class);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 9fc8aef..1bfb619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -140,7 +140,7 @@
* @return true if we want to intercept the dismissal of the entry, else false.
*/
boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children,
- IntConsumer removeCallback);
+ IntConsumer removeCallback, Executor callbackExecutor);
/** Set the proxy to commnuicate with SysUi side components. */
void setSysuiProxy(SysuiProxy proxy);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 5c3af3e..8ac9a7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -37,6 +37,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
/**
* Controller class of PiP animations (both from and to PiP mode).
@@ -112,6 +113,7 @@
PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart,
alphaEnd));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
+ && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds())
&& mCurrentAnimator.isRunning()) {
mCurrentAnimator.updateEndValue(alphaEnd);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index d4f229c..3af0ff0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -216,7 +216,7 @@
}
ArrayList<RemoteAction> mediaActions = new ArrayList<>();
- boolean isPlaying = mMediaController.getPlaybackState().isActive();
+ boolean isPlaying = mMediaController.getPlaybackState().isActiveState();
long actions = mMediaController.getPlaybackState().getActions();
// Prev action
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e152633..e66be66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -99,9 +99,10 @@
private enum State {
UNDEFINED(0),
TASK_APPEARED(1),
- ENTERING_PIP(2),
- ENTERED_PIP(3),
- EXITING_PIP(4);
+ ENTRY_SCHEDULED(2),
+ ENTERING_PIP(3),
+ ENTERED_PIP(4),
+ EXITING_PIP(5);
private final int mStateValue;
@@ -265,6 +266,13 @@
}
/**
+ * Returns whether the entry animation is waiting to be started.
+ */
+ public boolean isEntryScheduled() {
+ return mState == State.ENTRY_SCHEDULED;
+ }
+
+ /**
* Registers a callback when a display change has been detected when we enter PiP.
*/
public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
@@ -492,6 +500,19 @@
}
}
+ /**
+ * Called when the display rotation handling is skipped (e.g. when rotation happens while in
+ * the middle of an entry transition).
+ */
+ public void onDisplayRotationSkipped() {
+ if (isEntryScheduled()) {
+ // The PIP animation is scheduled to start with the previous orientation's bounds,
+ // re-calculate the entry bounds and restart the alpha animation.
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
+ }
+ }
+
@VisibleForTesting
void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
// If we are fading the PIP in, then we should move the pip to the final location as
@@ -501,6 +522,7 @@
mSurfaceControlTransactionFactory.getTransaction();
tx.setAlpha(mLeash, 0f);
tx.apply();
+ mState = State.ENTRY_SCHEDULED;
applyEnterPipSyncTransaction(destinationBounds, () -> {
mPipAnimationController
.getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 62ae1d5..f505e60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -116,7 +116,8 @@
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
if (!mPipTaskOrganizer.isInPip()
|| mPipBoundsState.getDisplayLayout().rotation() == toRotation
- || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
+ || mPipTaskOrganizer.isDeferringEnterPipAnimation()
+ || mPipTaskOrganizer.isEntryScheduled()) {
// Skip if the same rotation has been set or we aren't in PIP or haven't actually
// entered PIP yet. We still need to update the display layout in the bounds handler
// in this case.
@@ -124,6 +125,7 @@
// do not forget to update the movement bounds as well.
updateMovementBounds(mPipBoundsState.getNormalBounds(), true /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */, t);
+ mPipTaskOrganizer.onDisplayRotationSkipped();
return;
}
// If there is an animation running (ie. from a shelf offset), then ensure that we calculate
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 71f533c..ab00dd5 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -24,6 +24,7 @@
#include "SkClipStack.h"
#include "SkRect.h"
#include "SkM44.h"
+#include "utils/GLUtils.h"
namespace android {
namespace uirenderer {
@@ -170,6 +171,8 @@
setScissor(info.height, clipRegion.getBounds());
}
+ // WebView may swallow GL errors, so catch them here
+ GL_CHECKPOINT(LOW);
mWebViewHandle->drawGl(info);
if (clearStencilAfterFunctor) {
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 85d1bc5..4dc1cca 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -6887,7 +6887,6 @@
*
* @return true if successful, otherwise false
*/
- @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS)
public @EncodedSurroundOutputMode int getEncodedSurroundMode() {
try {
return getService().getEncodedSurroundMode(
@@ -6944,7 +6943,6 @@
*
* @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);
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 864350e..3a19b13 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -2495,7 +2495,10 @@
* The default security level is defined as the highest security level
* supported on the device.
*
- * @param mime The mime type of the media data
+ * @param mime The mime type of the media data. Please use {@link
+ * #isCryptoSchemeSupported(UUID, String)} to query mime type support separately;
+ * for unsupported mime types the return value of {@link
+ * #requiresSecureDecoder(String)} is crypto scheme dependent.
*/
public boolean requiresSecureDecoder(@NonNull String mime) {
return requiresSecureDecoder(mime, getMaxSecurityLevel());
@@ -2505,7 +2508,10 @@
* Query if the crypto scheme requires the use of a secure decoder
* to decode data of the given mime type at the given security level.
*
- * @param mime The mime type of the media data
+ * @param mime The mime type of the media data. Please use {@link
+ * #isCryptoSchemeSupported(UUID, String, int)} to query mime type support
+ * separately; for unsupported mime types the return value of {@link
+ * #requiresSecureDecoder(String, int)} is crypto scheme dependent.
* @param level a security level between {@link #SECURITY_LEVEL_SW_SECURE_CRYPTO}
* and {@link #SECURITY_LEVEL_HW_SECURE_ALL}. Otherwise the special value
* {@link #getMaxSecurityLevel()} is also permitted;
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 9eacc74..e7d30ebb 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -492,26 +493,15 @@
/**
* Returns whether this is considered as an active playback state.
- * <p>
- * The playback state is considered as an active if the state is one of the following:
- * <ul>
- * <li>{@link #STATE_BUFFERING}</li>
- * <li>{@link #STATE_CONNECTING}</li>
- * <li>{@link #STATE_FAST_FORWARDING}</li>
- * <li>{@link #STATE_PLAYING}</li>
- * <li>{@link #STATE_REWINDING}</li>
- * <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
- * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
- * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
- * </ul>
+ * @hide
*/
- public boolean isActive() {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public boolean isActiveState() {
switch (mState) {
case PlaybackState.STATE_FAST_FORWARDING:
case PlaybackState.STATE_REWINDING:
case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
case PlaybackState.STATE_SKIPPING_TO_NEXT:
- case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
case PlaybackState.STATE_BUFFERING:
case PlaybackState.STATE_CONNECTING:
case PlaybackState.STATE_PLAYING:
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 4719772..9e2cd3e 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -13,7 +13,7 @@
method @NonNull public static String getPrivateDnsMode(@NonNull android.content.Context);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackAsUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
- method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.os.Handler, @NonNull android.net.ConnectivityManager.NetworkCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void requestBackgroundNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler);
method @Deprecated public boolean requestRouteToHostAddress(int, java.net.InetAddress);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptPartialConnectivity(@NonNull android.net.Network, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAcceptUnvalidated(@NonNull android.net.Network, boolean, boolean);
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 63e0fe9..2194575 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -239,6 +239,7 @@
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 void setLingerDuration(@NonNull java.time.Duration);
method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index c4a0d69..96f2de6 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -5362,10 +5362,10 @@
* {@link #unregisterNetworkCallback(NetworkCallback)}.
*
* @param request {@link NetworkRequest} describing this request.
- * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
- * If null, the callback is invoked on the default internal Handler.
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
* the callback must not be shared - it uniquely specifies this request.
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
+ * If null, the callback is invoked on the default internal Handler.
* @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
* @throws SecurityException if missing the appropriate permissions.
* @throws RuntimeException if the app already has too many callbacks registered.
@@ -5380,7 +5380,8 @@
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
})
public void requestBackgroundNetwork(@NonNull NetworkRequest request,
- @NonNull Handler handler, @NonNull NetworkCallback networkCallback) {
+ @NonNull NetworkCallback networkCallback,
+ @SuppressLint("ListenerLast") @NonNull Handler handler) {
final NetworkCapabilities nc = request.networkCapabilities;
sendRequestForNetwork(nc, networkCallback, 0, BACKGROUND_REQUEST,
TYPE_NONE, new CallbackHandler(handler));
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index 26cb1ed..9a58add 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -42,4 +42,5 @@
void sendQosSessionLost(int qosCallbackId, in QosSession session);
void sendQosCallbackError(int qosCallbackId, int exceptionType);
void sendTeardownDelayMs(int teardownDelayMs);
+ void sendLingerDuration(int durationMs);
}
diff --git a/packages/Connectivity/framework/src/android/net/LinkProperties.java b/packages/Connectivity/framework/src/android/net/LinkProperties.java
index e41ed72..99f48b4 100644
--- a/packages/Connectivity/framework/src/android/net/LinkProperties.java
+++ b/packages/Connectivity/framework/src/android/net/LinkProperties.java
@@ -686,8 +686,8 @@
}
/**
- * Adds a {@link RouteInfo} to this {@code LinkProperties}, if a {@link RouteInfo}
- * with the same {@link RouteInfo.RouteKey} with different properties
+ * Adds a {@link RouteInfo} to this {@code LinkProperties}. If there is a {@link RouteInfo}
+ * with the same destination, gateway and interface with different properties
* (e.g., different MTU), it will be updated. If the {@link RouteInfo} had an
* interface name set and that differs from the interface set for this
* {@code LinkProperties} an {@link IllegalArgumentException} will be thrown.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 3622c1c..518d3f3 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
@@ -106,6 +107,9 @@
private final String LOG_TAG;
private static final boolean DBG = true;
private static final boolean VDBG = false;
+ /** @hide */
+ @TestApi
+ public static final int MIN_LINGER_TIMER_MS = 2000;
private final ArrayList<RegistryAction> mPreConnectedQueue = new ArrayList<>();
private volatile long mLastBwRefreshTime = 0;
private static final long BW_REFRESH_MIN_WIN_MS = 500;
@@ -391,6 +395,15 @@
*/
public static final int CMD_NETWORK_DESTROYED = BASE + 23;
+ /**
+ * Sent by the NetworkAgent to ConnectivityService to set the linger duration for this network
+ * agent.
+ * arg1 = the linger duration, represents by {@link Duration}.
+ *
+ * @hide
+ */
+ public static final int EVENT_LINGER_DURATION_CHANGED = BASE + 24;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
config.legacyTypeName, config.legacySubTypeName);
@@ -1287,6 +1300,22 @@
queueOrSendMessage(ra -> ra.sendQosCallbackError(qosCallbackId, exceptionType));
}
+ /**
+ * Set the linger duration for this network agent.
+ * @param duration the delay between the moment the network becomes unneeded and the
+ * moment the network is disconnected or moved into the background.
+ * Note that If this duration has greater than millisecond precision, then
+ * the internal implementation will drop any excess precision.
+ */
+ public void setLingerDuration(@NonNull final Duration duration) {
+ Objects.requireNonNull(duration);
+ final long durationMs = duration.toMillis();
+ if (durationMs < MIN_LINGER_TIMER_MS || durationMs > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Duration must be within ["
+ + MIN_LINGER_TIMER_MS + "," + Integer.MAX_VALUE + "]ms");
+ }
+ queueOrSendMessage(ra -> ra.sendLingerDuration((int) durationMs));
+ }
/** @hide */
protected void log(final String s) {
diff --git a/packages/Connectivity/framework/src/android/net/NetworkProvider.java b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
index cfb7325..0665af5 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java
@@ -167,7 +167,15 @@
ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(request);
}
- /** @hide */
+ /**
+ * A callback for parties registering a NetworkOffer.
+ *
+ * This is used with {@link ConnectivityManager#offerNetwork}. When offering a network,
+ * the system will use this callback to inform the caller that a network corresponding to
+ * this offer is needed or unneeded.
+ *
+ * @hide
+ */
@SystemApi
public interface NetworkOfferCallback {
/**
diff --git a/packages/Connectivity/framework/src/android/net/NetworkScore.java b/packages/Connectivity/framework/src/android/net/NetworkScore.java
index b0752e9..7be7deb 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkScore.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java
@@ -48,7 +48,14 @@
})
public @interface KeepConnectedReason { }
+ /**
+ * Do not keep this network connected if there is no outstanding request for it.
+ */
public static final int KEEP_CONNECTED_NONE = 0;
+ /**
+ * Keep this network connected even if there is no outstanding request for it, because it
+ * is being considered for handover.
+ */
public static final int KEEP_CONNECTED_FOR_HANDOVER = 1;
// Agent-managed policies
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
index dcbdc07..cec8b32 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles_preference.xml
@@ -15,7 +15,7 @@
limitations under the License.
-->
<resources>
- <!--DEPRECATED. It will remove after all of client team migrated to new style. -->
+ <!--DEPRECATED. It will be removed after all of client teams migrated to new style. -->
<style name="PreferenceTheme" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
<item name="preferenceStyle">@style/SettingsPreference</item>
@@ -28,7 +28,7 @@
<item name="footerPreferenceStyle">@style/Preference.Material</item>
</style>
- <style name="SettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay">
+ <style name="PreferenceTheme.SettingsBase" parent="@style/PreferenceThemeOverlay">
<item name="preferenceCategoryStyle">@style/SettingsCategoryPreference</item>
<item name="preferenceStyle">@style/SettingsPreference</item>
<item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 13c7523..9c096d2 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -21,7 +21,7 @@
<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/SettingsPreferenceTheme</item>
+ <item name="preferenceTheme">@style/PreferenceTheme.SettingsBase</item>
</style>
<!-- Using in SubSettings page including injected settings page -->
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 14ccd80..70b826a 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1014,6 +1014,9 @@
<!-- Settings item title to select whether to show transcoding notifications. [CHAR LIMIT=85] -->
<string name="transcode_notification">Show transcoding notifications</string>
+ <!-- Settings item title to select whether to disable cache for transcoding. [CHAR LIMIT=85] -->
+ <string name="transcode_disable_cache">Disable transcoding cache</string>
+
<!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
<string name="runningservices_settings_title">Running services</string>
<!-- Services settings screen, setting option summary for the user to go to the screen to view running services -->
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index bef6423f..f685d88 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -113,6 +113,8 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<!-- ACCESS_BACKGROUND_LOCATION is needed for testing purposes only. -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <!-- ACCESS_MTP is needed for testing purposes only. -->
+ <uses-permission android:name="android.permission.ACCESS_MTP" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -539,6 +541,9 @@
<!-- Permission required for CTS test - CtsRotationResolverServiceDeviceTestCases -->
<uses-permission android:name="android.permission.MANAGE_ROTATION_RESOLVER" />
+ <!-- Permission required for CTS test - CtsUwbTestCases -->
+ <uses-permission android:name="android.permission.UWB_PRIVILEGED" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index da78a7c..3363f8e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -313,12 +313,14 @@
}
context.mainExecutor.execute {
- startAnimation(remoteAnimationTargets, iRemoteAnimationFinishedCallback)
+ startAnimation(remoteAnimationTargets, remoteAnimationNonAppTargets,
+ iRemoteAnimationFinishedCallback)
}
}
private fun startAnimation(
remoteAnimationTargets: Array<out RemoteAnimationTarget>,
+ remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
iCallback: IRemoteAnimationFinishedCallback
) {
val window = remoteAnimationTargets.firstOrNull {
@@ -332,7 +334,7 @@
return
}
- val navigationBar = remoteAnimationTargets.firstOrNull {
+ val navigationBar = remoteAnimationNonAppTargets.firstOrNull {
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 35423a9..53ff9f0 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -58,5 +58,7 @@
/** View to which this plugin can be registered, in order to get updates. */
interface SmartspaceView {
void registerDataProvider(BcSmartspaceDataPlugin plugin);
+
+ void setPrimaryTextColor(int color);
}
}
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 00c27bf..1cef44b 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -79,7 +79,6 @@
android:id="@+id/left_aligned_notification_icon_container"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_shelf_height"
- android:layout_marginTop="@dimen/widget_vertical_padding"
android:layout_below="@id/keyguard_status_area"
android:paddingStart="@dimen/below_clock_padding_start"
/>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index f35f5d1..6725a26 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -15,10 +15,12 @@
~ limitations under the License.
-->
+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<solid
- android:color="@color/notif_pill_background"
+ android:color="?androidprv:attr/colorSurface"
/>
<corners android:radius="20dp" />
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index dc9d920..bb82f91 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -17,10 +17,11 @@
<com.android.systemui.statusbar.notification.row.NotificationSnooze
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
- android:background="?android:attr/colorBackground"
+ android:background="?androidprv:attr/colorSurface"
android:theme="@style/Theme.SystemUI">
<RelativeLayout
@@ -34,7 +35,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingStart="@*android:dimen/notification_content_margin_end"
android:textColor="?android:attr/textColorPrimary"
android:paddingEnd="4dp"/>
diff --git a/packages/SystemUI/res/layout/notification_snooze_option.xml b/packages/SystemUI/res/layout/notification_snooze_option.xml
index f203839..d42cc02 100644
--- a/packages/SystemUI/res/layout/notification_snooze_option.xml
+++ b/packages/SystemUI/res/layout/notification_snooze_option.xml
@@ -17,8 +17,8 @@
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_marginStart="@*android:dimen/notification_content_margin_start"
+ android:layout_height="@*android:dimen/notification_headerless_min_height"
+ android:layout_marginStart="@*android:dimen/notification_content_margin_end"
android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
android:gravity="center_vertical"
android:textSize="14sp"
diff --git a/packages/SystemUI/res/layout/people_space_initial_layout.xml b/packages/SystemUI/res/layout/people_space_initial_layout.xml
new file mode 100644
index 0000000..ec29d18
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_initial_layout.xml
@@ -0,0 +1,69 @@
+<!--
+ ~ 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:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:theme="@android:style/Theme.DeviceDefault.DayNight"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:background="@drawable/people_space_tile_view_card"
+ android:id="@+id/item"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_gravity="top"
+ android:paddingVertical="16dp"
+ android:paddingHorizontal="16dp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:paddingEnd="20dp"
+ android:gravity="bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:background="@drawable/ic_person"
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
+
+ <TextView
+ android:id="@+id/name"
+ android:paddingTop="2dp"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <TextView
+ android:text="@string/status_before_loading"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="12sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="3"
+ android:ellipsize="end" />
+ </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
index b85d6b0..2402bd7 100644
--- a/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
+++ b/packages/SystemUI/res/layout/people_space_placeholder_layout.xml
@@ -14,63 +14,56 @@
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
+
<LinearLayout
android:background="@drawable/people_space_tile_view_card"
android:id="@+id/item"
- android:orientation="vertical"
+ android:orientation="horizontal"
+ android:gravity="center"
+ android:layout_gravity="top"
+ android:paddingVertical="16dp"
+ android:paddingHorizontal="16dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
- android:orientation="horizontal"
- android:gravity="center"
- android:paddingVertical="2dp"
- android:paddingHorizontal="8dp"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:orientation="vertical"
+ android:paddingEnd="20dp"
+ android:gravity="bottom"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
<ImageView
android:background="@drawable/ic_person"
- android:id="@+id/person_icon_only"
- android:layout_width="60dp"
- android:layout_height="60dp"/>
+ android:layout_width="48dp"
+ android:layout_height="48dp" />
- <LinearLayout
- android:orientation="vertical"
- android:paddingStart="8dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/availability"
- android:layout_width="10dp"
- android:layout_height="10dp"
- android:background="@drawable/circle_green_10dp"/>
- <TextView
- android:id="@+id/name"
- android:text="@string/empty_user_name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="14sp"
- android:maxLines="1"
- android:ellipsize="end"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-
- <TextView
- android:id="@+id/last_interaction"
- android:text="@string/empty_status"
- android:textColor="?android:attr/textColorSecondary"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textSize="12sp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:maxLines="3"
- android:ellipsize="end"/>
- </LinearLayout>
+ <TextView
+ android:id="@+id/name"
+ android:paddingTop="2dp"
+ android:text="@string/empty_user_name"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="12sp"
+ android:maxLines="1"
+ android:ellipsize="end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
</LinearLayout>
+
+ <TextView
+ android:text="@string/empty_status"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textSize="12sp"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:maxLines="3"
+ android:ellipsize="end" />
</LinearLayout>
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_customize_panel_content.xml b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
index 7cce1ba..6a1be81 100644
--- a/packages/SystemUI/res/layout/qs_customize_panel_content.xml
+++ b/packages/SystemUI/res/layout/qs_customize_panel_content.xml
@@ -45,8 +45,6 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
- android:paddingLeft="@dimen/qs_tile_layout_margin_side"
- android:paddingRight="@dimen/qs_tile_layout_margin_side"
android:paddingBottom="28dp"
android:clipToPadding="false"
android:scrollIndicators="top"
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 0217972..343b398 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -16,8 +16,7 @@
-->
<!-- Extends FrameLayout -->
-<com.android.systemui.qs.QSFooterView
- xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_footer_height"
@@ -32,77 +31,70 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:gravity="end" >
+ android:orientation="vertical">
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/qs_footer_actions_edit_container"
- android:layout_width="@integer/qs_footer_actions_width"
- android:layout_height="match_parent"
- android:layout_weight="@integer/qs_footer_actions_weight"
- android:gravity="center_vertical|start" >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/build"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/qs_tile_margin_horizontal"
+ android:paddingEnd="4dp"
+ android:layout_weight="1"
+ android:clickable="true"
+ android:ellipsize="marquee"
+ android:focusable="true"
+ android:gravity="center_vertical"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status"
+ android:visibility="gone" />
+
+ <com.android.systemui.qs.PageIndicator
+ android:id="@+id/footer_page_indicator"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:visibility="gone" />
+
+ <View
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/qs_footer_actions_container"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical">
+
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@android:id/edit"
- android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="?android:attr/selectableItemBackgroundBorderless"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
android:clickable="true"
android:clipToPadding="false"
android:contentDescription="@string/accessibility_quick_settings_edit"
android:focusable="true"
android:padding="@dimen/qs_footer_icon_padding"
android:src="@*android:drawable/ic_mode_edit"
- android:tint="?android:attr/colorForeground"/>
-
- <TextView
- android:id="@+id/build"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:clickable="true"
- android:gravity="center_vertical"
- android:focusable="true"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:textAppearance="@style/TextAppearance.QS.Status"
- android:layout_marginEnd="4dp"
- android:visibility="gone"/>
- </com.android.keyguard.AlphaOptimizedLinearLayout>
-
- <com.android.systemui.qs.PageIndicator
- android:id="@+id/footer_page_indicator"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:visibility="gone" />
-
- <com.android.keyguard.AlphaOptimizedLinearLayout
- android:id="@+id/qs_footer_actions_container"
- android:layout_width="@integer/qs_footer_actions_width"
- android:layout_height="match_parent"
- android:layout_weight="@integer/qs_footer_actions_weight"
- android:gravity="center_vertical|end" >
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="?android:attr/selectableItemBackgroundBorderless"
- android:clickable="true"
- android:clipToPadding="false"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:tint="?android:attr/colorForeground"
- android:visibility="gone"
- />
+ android:tint="?android:attr/colorForeground" />
<com.android.systemui.statusbar.phone.MultiUserSwitch
android:id="@+id/multi_user_switch"
- android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_alignParentEnd="true"
- android:background="@drawable/ripple_drawable"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
android:focusable="true">
<ImageView
@@ -110,40 +102,58 @@
android:layout_width="@dimen/multi_user_avatar_expanded_size"
android:layout_height="@dimen/multi_user_avatar_expanded_size"
android:layout_gravity="center"
- android:scaleType="centerInside"/>
+ android:scaleType="centerInside" />
</com.android.systemui.statusbar.phone.MultiUserSwitch>
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/settings_button_container"
- android:layout_width="@dimen/qs_footer_action_button_size"
+ android:layout_width="0dp"
android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:layout_weight="1"
android:clipChildren="false"
android:clipToPadding="false">
<com.android.systemui.statusbar.phone.SettingsButton
android:id="@+id/settings_button"
- style="@android:style/Widget.Material.Button.Borderless"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="@dimen/qs_footer_action_button_size"
android:layout_gravity="center"
- android:padding="@dimen/qs_footer_icon_padding"
- android:background="@drawable/ripple_drawable"
android:contentDescription="@string/accessibility_quick_settings_settings"
- android:src="@drawable/ic_settings"
+ android:background="@drawable/qs_footer_action_chip_background_borderless"
+ android:padding="@dimen/qs_footer_icon_padding"
android:scaleType="centerInside"
- android:tint="?android:attr/colorForeground"/>
+ android:src="@drawable/ic_settings"
+ android:tint="?android:attr/colorForeground" />
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/tuner_icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:paddingStart="36dp"
- android:paddingEnd="4dp"
+ android:layout_width="8dp"
+ android:layout_height="8dp"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_marginBottom="@dimen/qs_footer_icon_padding"
android:src="@drawable/tuner"
android:tint="?android:attr/textColorTertiary"
- android:visibility="invisible"/>
+ android:visibility="invisible" />
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
- </com.android.keyguard.AlphaOptimizedLinearLayout>
+
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/pm_lite"
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_footer_action_button_size"
+ android:layout_weight="1"
+ android:background="@drawable/qs_footer_action_chip_background"
+ android:clickable="true"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:padding="@dimen/qs_footer_icon_padding"
+ android:src="@*android:drawable/ic_lock_power_off"
+ android:contentDescription="@string/accessibility_quick_settings_power_menu"
+ android:tint="?android:attr/colorForeground" />
+
+ </LinearLayout>
</LinearLayout>
+
</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml b/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml
deleted file mode 100644
index 343b398..0000000
--- a/packages/SystemUI/res/layout/qs_footer_impl_two_lines.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** 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.
--->
-
-<!-- Extends FrameLayout -->
-<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/qs_footer"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
- android:layout_marginStart="@dimen/qs_footer_margin"
- android:layout_marginEnd="@dimen/qs_footer_margin"
- android:background="@android:color/transparent"
- android:baselineAligned="false"
- android:clickable="false"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:id="@+id/build"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:paddingStart="@dimen/qs_tile_margin_horizontal"
- android:paddingEnd="4dp"
- android:layout_weight="1"
- android:clickable="true"
- android:ellipsize="marquee"
- android:focusable="true"
- android:gravity="center_vertical"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.QS.Status"
- android:visibility="gone" />
-
- <com.android.systemui.qs.PageIndicator
- android:id="@+id/footer_page_indicator"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical"
- android:visibility="gone" />
-
- <View
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1" />
-
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/qs_footer_actions_container"
- android:layout_width="match_parent"
- android:layout_height="48dp"
- android:gravity="center_vertical">
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@android:id/edit"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:contentDescription="@string/accessibility_quick_settings_edit"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_mode_edit"
- android:tint="?android:attr/colorForeground" />
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_expanded_size"
- android:layout_height="@dimen/multi_user_avatar_expanded_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/settings_button_container"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_marginEnd="@dimen/qs_tile_margin_horizontal"
- android:background="@drawable/qs_footer_action_chip_background"
- android:layout_weight="1"
- android:clipChildren="false"
- android:clipToPadding="false">
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_gravity="center"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:background="@drawable/qs_footer_action_chip_background_borderless"
- android:padding="@dimen/qs_footer_icon_padding"
- android:scaleType="centerInside"
- android:src="@drawable/ic_settings"
- android:tint="?android:attr/colorForeground" />
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/tuner_icon"
- android:layout_width="8dp"
- android:layout_height="8dp"
- android:layout_gravity="center_horizontal|bottom"
- android:layout_marginBottom="@dimen/qs_footer_icon_padding"
- android:src="@drawable/tuner"
- android:tint="?android:attr/textColorTertiary"
- android:visibility="invisible" />
-
- </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
- <com.android.systemui.statusbar.AlphaOptimizedImageView
- android:id="@+id/pm_lite"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:layout_weight="1"
- android:background="@drawable/qs_footer_action_chip_background"
- android:clickable="true"
- android:clipToPadding="false"
- android:focusable="true"
- android:padding="@dimen/qs_footer_icon_padding"
- android:src="@*android:drawable/ic_lock_power_off"
- android:contentDescription="@string/accessibility_quick_settings_power_menu"
- android:tint="?android:attr/colorForeground" />
-
- </LinearLayout>
- </LinearLayout>
-
-</com.android.systemui.qs.QSFooterView>
diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml
index 5c8b2b0..c830773 100644
--- a/packages/SystemUI/res/layout/qs_paged_page.xml
+++ b/packages/SystemUI/res/layout/qs_paged_page.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ 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.
@@ -14,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<view
- class="com.android.systemui.qs.PagedTileLayout$TilePage"
+<com.android.systemui.qs.SideLabelTileLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tile_page"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
deleted file mode 100644
index c830773..0000000
--- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml
+++ /dev/null
@@ -1,23 +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.
--->
-<com.android.systemui.qs.SideLabelTileLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/tile_page"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false" />
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
index 46a7cf6..c3f1113 100644
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
+++ b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2015 The Android Open Source Project
+ 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.
@@ -22,5 +22,4 @@
android:layout_height="0dp"
android:layout_weight="1"
android:clipChildren="true"
- android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom">
-</com.android.systemui.qs.PagedTileLayout>
+ android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom" />
diff --git a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
deleted file mode 100644
index efa2403..0000000
--- a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<com.android.systemui.qs.PagedTileLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto"
- android:id="@+id/qs_pager"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:clipChildren="true"
- android:paddingBottom="@dimen/qs_paged_tile_layout_padding_bottom"
- systemui:sideLabels="true" />
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index dc595ee..3d2a621 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -43,10 +43,7 @@
android:background="@android:color/transparent"
android:focusable="true"
android:accessibilityTraversalBefore="@android:id/edit">
- <ViewStub
- android:id="@+id/qs_footer_stub"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ <include layout="@layout/qs_footer_impl" />
<include layout="@layout/qs_media_divider"
android:id="@+id/divider"/>
</com.android.systemui.qs.QSPanel>
@@ -59,18 +56,4 @@
<include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
android:visibility="gone" />
- <FrameLayout
- android:id="@+id/qs_drag_handle_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:elevation="4dp"
- android:paddingBottom="5dp">
- <View
- android:layout_width="46dp"
- android:layout_height="3dp"
- android:background="@drawable/qs_footer_drag_handle" />
- </FrameLayout>
-
-
</com.android.systemui.qs.QSContainerImpl>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 5c77d16..91220e5 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -46,6 +46,7 @@
android:layout_gravity="end"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
+ android:textColor="@color/notif_pill_text"
android:contentDescription="@string/accessibility_clear_all"
android:text="@string/clear_all_notifications_text"
/>
diff --git a/packages/SystemUI/res/values-h740dp-port/dimens.xml b/packages/SystemUI/res/values-h740dp-port/dimens.xml
deleted file mode 100644
index 966066f..0000000
--- a/packages/SystemUI/res/values-h740dp-port/dimens.xml
+++ /dev/null
@@ -1,27 +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
- -->
-
-<resources>
- <dimen name="qs_tile_height">106dp</dimen>
- <dimen name="qs_tile_margin_vertical">24dp</dimen>
-
- <!-- The height of the qs customize header. Should be
- (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
- (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
- -->
- <dimen name="qs_customize_header_min_height">46dp</dimen>
- <dimen name="qs_tile_margin_top">18dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml
index 46ec23c..ea456d8 100644
--- a/packages/SystemUI/res/values-land/config.xml
+++ b/packages/SystemUI/res/values-land/config.xml
@@ -20,12 +20,11 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- The maximum number of tiles in the QuickQSPanel -->
- <integer name="quick_qs_panel_max_columns">6</integer>
-
<!-- The maximum number of rows in the QuickSettings -->
<integer name="quick_settings_max_rows">2</integer>
+ <integer name="quick_settings_num_columns">4</integer>
+
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">2</integer>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 51d7b8e..007f81b 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -23,12 +23,16 @@
<dimen name="docked_divider_handle_height">16dp</dimen>
<dimen name="qs_tile_margin_top">8dp</dimen>
- <dimen name="qs_tile_margin_vertical">0dp</dimen>
+
+ <!-- The height of the qs customize header. Should be
+ (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (8dp)) -
+ (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (4dp))
+ -->
+ <dimen name="qs_customize_header_min_height">44dp</dimen>
<dimen name="battery_detail_graph_space_top">9dp</dimen>
<dimen name="battery_detail_graph_space_bottom">9dp</dimen>
- <integer name="quick_settings_num_columns">4</integer>
<dimen name="qs_detail_margin_top">0dp</dimen>
<dimen name="volume_tool_tip_right_margin">136dp</dimen>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index e6c5bd0..8f88950 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -33,7 +33,6 @@
<!-- The color of the text inside a notification -->
<color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
- <color name="notif_pill_background">@*android:color/surface_dark</color>
<color name="notif_pill_text">@android:color/system_neutral1_50</color>
<color name="notification_guts_link_icon_tint">@color/GM2_grey_500</color>
diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml
index a6321fe..e2b2e25 100644
--- a/packages/SystemUI/res/values-sw600dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/config.xml
@@ -15,8 +15,6 @@
~ limitations under the License
-->
<resources>
- <integer name="quick_settings_num_columns">3</integer>
-
<!-- Max number of columns for quick controls area -->
<integer name="controls_max_columns">2</integer>
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
deleted file mode 100644
index 302e5e4..0000000
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (c) 2012, 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="keyguard_clock_notifications_margin">36dp</dimen>
-
- <dimen name="keyguard_indication_margin_bottom">80dp</dimen>
-
- <!-- Screen pinning request width (just a little bit bigger than the three buttons here -->
- <dimen name="screen_pinning_request_width">490dp</dimen>
- <!-- Screen pinning request bottom button circle widths -->
- <dimen name="screen_pinning_request_button_width">162dp</dimen>
- <!-- Screen pinning request, controls padding on bigger screens, bigger nav bar -->
- <dimen name="screen_pinning_request_frame_padding">39dp</dimen>
- <!-- Screen pinning request side views to match nav bar
- In sw600dp we want the buttons centered so this fills the space,
- (screen_pinning_request_width - 3 * screen_pinning_request_button_width) / 2 -->
- <dimen name="screen_pinning_request_side_width">2dp</dimen>
-
- <dimen name="navigation_key_width">162dp</dimen>
- <dimen name="navigation_key_padding">42dp</dimen>
-
- <dimen name="battery_detail_graph_space_top">27dp</dimen>
- <dimen name="battery_detail_graph_space_bottom">27dp</dimen>
-
- <dimen name="qs_tile_margin_top">32dp</dimen>
- <dimen name="qs_brightness_padding_top">6dp</dimen>
- <dimen name="qs_detail_margin_top">28dp</dimen>
-
- <!-- In split shade mode notifications should be aligned to QS header so the value should be
- adjusted to qs header height and height of centered content inside of it:
- (quick_qs_offset_height (48dp) - ongoing_appops_chip_height (24dp) ) / 2 -->
- <dimen name="notifications_top_padding_split_shade">12dp</dimen>
-</resources>
diff --git a/packages/SystemUI/res/values-w550dp-land/config.xml b/packages/SystemUI/res/values-w550dp-land/config.xml
deleted file mode 100644
index a33f131..0000000
--- a/packages/SystemUI/res/values-w550dp-land/config.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2016, 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.
-*/
--->
-
-<!-- These resources are around just to allow their values to be customized
- for different hardware and product builds. -->
-<resources>
- <integer name="quick_settings_num_columns">6</integer>
-</resources>
diff --git a/packages/SystemUI/res/values-w650dp-land/dimens.xml b/packages/SystemUI/res/values-w650dp-land/dimens.xml
index 108d6cf..97b6da1 100644
--- a/packages/SystemUI/res/values-w650dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-w650dp-land/dimens.xml
@@ -16,6 +16,5 @@
-->
<resources>
<!-- Standard notification width + gravity -->
- <dimen name="notification_panel_width">644dp</dimen>
-
+ <dimen name="notification_panel_width">-1px</dimen> <!-- match_parent -->
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index e4bdbf3..f489fe8 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -176,10 +176,6 @@
<attr name="android:alpha" />
</declare-styleable>
- <declare-styleable name="PagedTileLayout">
- <attr name="sideLabels" format="boolean"/>
- </declare-styleable>
-
<declare-styleable name="CropView">
<attr name="handleThickness" />
<attr name="handleColor" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index f699198..bf13c21 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -157,7 +157,6 @@
<color name="minimize_dock_shadow_end">#00000000</color>
<color name="default_remote_input_background">@*android:color/notification_default_color</color>
- <color name="notif_pill_background">@*android:color/surface_light</color>
<color name="notif_pill_text">@android:color/system_neutral1_900</color>
<color name="remote_input_accent">?android:attr/colorAccent</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 2355650..5feb957 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -86,13 +86,13 @@
<bool name="config_navigation_bar_enable_auto_dim_no_visible_wallpaper">true</bool>
<!-- The maximum number of tiles in the QuickQSPanel -->
- <integer name="quick_qs_panel_max_columns">6</integer>
+ <integer name="quick_qs_panel_max_columns">4</integer>
<!-- The number of columns in the QuickSettings -->
- <integer name="quick_settings_num_columns">3</integer>
+ <integer name="quick_settings_num_columns">2</integer>
<!-- The number of rows in the QuickSettings -->
- <integer name="quick_settings_max_rows">3</integer>
+ <integer name="quick_settings_max_rows">4</integer>
<!-- The number of columns that the top level tiles span in the QuickSettings -->
<integer name="quick_settings_user_time_settings_tile_span">1</integer>
@@ -656,4 +656,10 @@
<!-- Y -->
<!-- radius -->
</integer-array>
+
+ <!-- Overrides the behavior of the face unlock keyguard bypass setting:
+ 0 - Don't override the setting (default)
+ 1 - Override the setting to always bypass keyguard
+ 2 - Override the setting to never bypass keyguard -->
+ <integer name="config_face_unlock_bypass_override">0</integer>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index ad00882..62ac75e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -407,10 +407,10 @@
<!-- The height of the quick settings footer that holds the user switcher, settings icon,
etc. -->
- <dimen name="qs_footer_height">48dp</dimen>
+ <dimen name="qs_footer_height">96dp</dimen>
<!-- The size of each of the icon buttons in the QS footer -->
- <dimen name="qs_footer_action_button_size">@dimen/qs_footer_height</dimen>
+ <dimen name="qs_footer_action_button_size">48dp</dimen>
<dimen name="qs_footer_action_corner_radius">20dp</dimen>
@@ -529,25 +529,25 @@
<dimen name="pull_span_min">25dp</dimen>
<dimen name="qs_corner_radius">14dp</dimen>
- <dimen name="qs_tile_height">96dp</dimen>
+ <dimen name="qs_tile_height">88dp</dimen>
<!--notification_side_paddings + notification_content_margin_start - (qs_quick_tile_size - qs_tile_background_size) / 2 -->
<dimen name="qs_tile_layout_margin_side">18dp</dimen>
- <dimen name="qs_tile_margin_horizontal">18dp</dimen>
+ <dimen name="qs_tile_margin_horizontal">8dp</dimen>
<dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
- <dimen name="qs_tile_margin_vertical">2dp</dimen>
- <dimen name="qs_tile_margin_top_bottom">12dp</dimen>
- <dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
+ <dimen name="qs_tile_margin_vertical">@dimen/qs_tile_margin_horizontal</dimen>
+ <dimen name="qs_tile_margin_top_bottom">4dp</dimen>
+ <dimen name="qs_tile_margin_top_bottom_negative">-4dp</dimen>
<!-- The height of the qs customize header. Should be
- (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (0dp)) -
- (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (12dp))
+ (qs_panel_padding_top (48dp) + brightness_mirror_height (48dp) + qs_tile_margin_top (18dp)) -
+ (Toolbar_minWidth (56dp) + qs_tile_margin_top_bottom (4dp))
-->
- <dimen name="qs_customize_header_min_height">28dp</dimen>
- <dimen name="qs_tile_margin_top">0dp</dimen>
+ <dimen name="qs_customize_header_min_height">54dp</dimen>
+ <dimen name="qs_tile_margin_top">18dp</dimen>
<dimen name="qs_tile_icon_background_stroke_width">-1dp</dimen>
- <dimen name="qs_tile_background_size">44dp</dimen>
+ <dimen name="qs_tile_background_size">56dp</dimen>
<dimen name="qs_icon_size">20dp</dimen>
<dimen name="qs_label_container_margin">10dp</dimen>
- <dimen name="qs_quick_tile_size">48dp</dimen>
+ <dimen name="qs_quick_tile_size">60dp</dimen>
<dimen name="qs_quick_tile_padding">12dp</dimen>
<dimen name="qs_header_gear_translation">16dp</dimen>
<dimen name="qs_header_tile_margin_bottom">18dp</dimen>
@@ -557,9 +557,9 @@
Scaled @dimen/qs_page_indicator-width by .4f.
-->
<dimen name="qs_page_indicator_dot_width">6.4dp</dimen>
- <dimen name="qs_tile_side_label_padding">6dp</dimen>
+ <dimen name="qs_tile_side_label_padding">12dp</dimen>
<dimen name="qs_tile_icon_size">24dp</dimen>
- <dimen name="qs_tile_text_size">12sp</dimen>
+ <dimen name="qs_tile_text_size">14sp</dimen>
<dimen name="qs_tile_divider_height">1dp</dimen>
<dimen name="qs_panel_padding">16dp</dimen>
<dimen name="qs_dual_tile_height">112dp</dimen>
@@ -570,7 +570,7 @@
<dimen name="qs_tile_padding_bottom">16dp</dimen>
<dimen name="qs_tile_spacing">4dp</dimen>
<dimen name="qs_panel_padding_bottom">0dp</dimen>
- <dimen name="qs_panel_padding_top">@dimen/qs_header_tooltip_height</dimen>
+ <dimen name="qs_panel_padding_top">48dp</dimen>
<dimen name="qs_detail_header_height">56dp</dimen>
<dimen name="qs_detail_header_padding">0dp</dimen>
<dimen name="qs_detail_image_width">56dp</dimen>
@@ -594,7 +594,6 @@
<dimen name="qs_detail_item_icon_width">32dp</dimen>
<dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
<dimen name="qs_detail_item_icon_marginEnd">20dp</dimen>
- <dimen name="qs_header_tooltip_height">48dp</dimen>
<dimen name="qs_header_alarm_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
<dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
<dimen name="qs_header_alarm_text_margin_start">6dp</dimen>
@@ -1415,17 +1414,17 @@
<dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
- <dimen name="rounded_slider_height">44dp</dimen>
+ <dimen name="rounded_slider_height">48dp</dimen>
<!-- rounded_slider_height / 2 -->
- <dimen name="rounded_slider_corner_radius">22dp</dimen>
+ <dimen name="rounded_slider_corner_radius">24dp</dimen>
<dimen name="rounded_slider_icon_size">20dp</dimen>
<!-- (rounded_slider_height - rounded_slider_icon_size) / 2 -->
- <dimen name="rounded_slider_icon_inset">12dp</dimen>
+ <dimen name="rounded_slider_icon_inset">14dp</dimen>
<!-- rounded_slider_corner_radius - rounded_slider_track_corner_radius -->
- <dimen name="rounded_slider_track_inset">18dp</dimen>
- <dimen name="rounded_slider_track_width">8dp</dimen>
+ <dimen name="rounded_slider_track_inset">22dp</dimen>
+ <dimen name="rounded_slider_track_width">4dp</dimen>
<!-- rounded_slider_track_width / 2 -->
- <dimen name="rounded_slider_track_corner_radius">4dp</dimen>
+ <dimen name="rounded_slider_track_corner_radius">2dp</dimen>
<!-- inset for ic_lock_open within a DisabledUdfpsView -->
<dimen name="udfps_unlock_icon_inset">16dp</dimen>
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index bbf2048..5827f4e 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -28,8 +28,6 @@
<!-- b/171917882 -->
<bool name="flag_notification_twocolumn">false</bool>
- <bool name="flag_qs_labels">false</bool>
-
<!-- AOD/Lockscreen alternate layout -->
<bool name="flag_keyguard_layout">false</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4ae1c936..e55142b 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2012,7 +2012,7 @@
<string name="notification_menu_settings_action">Settings</string>
<!-- Notification: Snooze panel: Snooze undo button label. [CHAR LIMIT=50]-->
- <string name="snooze_undo">UNDO</string>
+ <string name="snooze_undo">Undo</string>
<!-- Notification: Snooze panel: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
<string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
@@ -2953,10 +2953,7 @@
[CHAR LIMIT=NONE] -->
<string name="battery_state_unknown_notification_text">Tap for more information</string>
- <!-- No translation [CHAR LIMIT=0] -->
- <string name="qs_remove_labels" translatable="false"></string>
-
- <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamily</string>
+ <string name="qs_tile_label_fontFamily" translatable="false">@*android:string/config_headlineFontFamilyMedium</string>
<!-- Secondary label for alarm tile when there is no next alarm information [CHAR LIMIT=20] -->
<string name="qs_alarm_tile_no_alarm">No alarm set</string>
diff --git a/packages/SystemUI/res/xml/people_space_widget_info.xml b/packages/SystemUI/res/xml/people_space_widget_info.xml
index e386147..68e6ca8 100644
--- a/packages/SystemUI/res/xml/people_space_widget_info.xml
+++ b/packages/SystemUI/res/xml/people_space_widget_info.xml
@@ -26,5 +26,5 @@
android:previewLayout="@layout/people_space_placeholder_layout"
android:resizeMode="horizontal|vertical"
android:configure="com.android.systemui.people.PeopleSpaceActivity"
- android:initialLayout="@layout/people_space_placeholder_layout">
+ android:initialLayout="@layout/people_space_initial_layout">
</appwidget-provider>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index cb825c0..f89e365 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -26,14 +26,18 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Color;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
+import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.clock.ClockManager;
+import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -50,6 +54,7 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
import java.util.Locale;
@@ -87,6 +92,10 @@
private Executor mUiExecutor;
private SmartspaceSession mSmartspaceSession;
private SmartspaceSession.Callback mSmartspaceCallback;
+ private float mDozeAmount;
+ private int mWallpaperTextColor;
+ private int mDozeColor = Color.WHITE;
+ private ConfigurationController mConfigurationController;
/**
* Listener for changes to the color palette.
@@ -103,8 +112,25 @@
}
};
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onThemeChanged() {
+ updateWallpaperColor();
+ }
+ };
+
private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
+ new StatusBarStateController.StateListener() {
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ mDozeAmount = eased;
+ updateSmartspaceColor();
+ }
+ };
+
// If set, will replace keyguard_status_area
private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
@@ -121,7 +147,8 @@
PluginManager pluginManager,
FeatureFlags featureFlags,
@Main Executor uiExecutor,
- BatteryController batteryController) {
+ BatteryController batteryController,
+ ConfigurationController configurationController) {
super(keyguardClockSwitch);
mResources = resources;
mStatusBarStateController = statusBarStateController;
@@ -134,6 +161,7 @@
mIsSmartspaceEnabled = featureFlags.isSmartspaceEnabled();
mUiExecutor = uiExecutor;
mBatteryController = batteryController;
+ mConfigurationController = configurationController;
}
/**
@@ -172,6 +200,12 @@
mBatteryController);
mLargeClockViewController.init();
+ mDozeAmount = mStatusBarStateController.getDozeAmount();
+ updateWallpaperColor();
+
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mConfigurationController.addCallback(mConfigurationListener);
+
// If a smartspace plugin is detected, replace the existing smartspace
// (keyguard_status_area), and initialize a new session
mPluginListener = new PluginListener<BcSmartspaceDataPlugin>() {
@@ -186,6 +220,7 @@
mSmartspaceView = plugin.getView(mView);
mSmartspaceView.registerDataProvider(plugin);
+ updateSmartspaceColor();
View asView = (View) mSmartspaceView;
// Place plugin view below normal clock...
@@ -242,6 +277,19 @@
mPluginManager.addPluginListener(mPluginListener, BcSmartspaceDataPlugin.class, false);
}
+ private void updateWallpaperColor() {
+ mWallpaperTextColor = Utils.getColorAttrDefaultColor(getContext(),
+ R.attr.wallpaperTextColor);
+ updateSmartspaceColor();
+ }
+
+ private void updateSmartspaceColor() {
+ if (mSmartspaceView != null) {
+ int color = ColorUtils.blendARGB(mWallpaperTextColor, mDozeColor, mDozeAmount);
+ mSmartspaceView.setPrimaryTextColor(color);
+ }
+ }
+
@Override
protected void onViewDetached() {
if (CUSTOM_CLOCKS_ENABLED) {
@@ -256,6 +304,8 @@
mSmartspaceSession = null;
}
mPluginManager.removePluginListener(mPluginListener);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mConfigurationController.removeCallback(mConfigurationListener);
}
/**
@@ -399,4 +449,9 @@
private int getCurrentLayoutDirection() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
}
+
+ @VisibleForTesting
+ ConfigurationController.ConfigurationListener getConfigurationListener() {
+ return mConfigurationListener;
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 8cd68ef..ab15630 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -18,7 +18,7 @@
val isFaceDisabled: Boolean,
val isBecauseCannotSkipBouncer: Boolean,
val isKeyguardGoingAway: Boolean,
- val isFaceSettingEnabledForUser: Boolean,
+ val isBiometricSettingEnabledForUser: Boolean,
val isLockIconPressed: Boolean,
val isScanningAllowedByStrongAuth: Boolean,
val isPrimaryUser: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 67ee1f4..138dd15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -353,18 +353,15 @@
}
};
- private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray();
+ private SparseBooleanArray mBiometricEnabledForUser = new SparseBooleanArray();
private BiometricManager mBiometricManager;
private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
new IBiometricEnabledOnKeyguardCallback.Stub() {
@Override
- public void onChanged(BiometricSourceType type, boolean enabled, int userId)
- throws RemoteException {
+ public void onChanged(boolean enabled, int userId) throws RemoteException {
mHandler.post(() -> {
- if (type == BiometricSourceType.FACE) {
- mFaceSettingEnabledForUser.put(userId, enabled);
- updateFaceListeningState();
- }
+ mBiometricEnabledForUser.put(userId, enabled);
+ updateBiometricListeningState();
});
}
};
@@ -1119,6 +1116,7 @@
containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW)
|| containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
final boolean isEncrypted = containsFlag(strongAuth, STRONG_AUTH_REQUIRED_AFTER_BOOT);
+
return isEncrypted || isLockDown;
}
@@ -1359,7 +1357,7 @@
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
"com.android.systemui:AOD_INTERRUPT_END");
}
- mAuthController.onCancelAodInterrupt();
+ mAuthController.onCancelUdfps();
mIsUdfpsRunningWhileDozing = false;
}
};
@@ -1631,6 +1629,12 @@
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
}
+ @VisibleForTesting
+ void resetBiometricListeningState() {
+ mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ }
+
private void registerRingerTracker() {
mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
}
@@ -1951,7 +1955,7 @@
mIsFaceEnrolled = whitelistIpcs(
() -> mFaceManager != null && mFaceManager.isHardwareDetected()
&& mFaceManager.hasEnrolledTemplates(userId)
- && mFaceSettingEnabledForUser.get(userId));
+ && mBiometricEnabledForUser.get(userId));
}
/**
@@ -2099,7 +2103,7 @@
shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming))
&& !mSwitchingUser && !isFingerprintDisabled(getCurrentUser())
&& (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser
- && allowedOnBouncer;
+ && allowedOnBouncer && mBiometricEnabledForUser.get(getCurrentUser());
return shouldListen;
}
@@ -2158,7 +2162,7 @@
(mBouncer || mAuthInterruptActive || awakeKeyguard
|| shouldListenForFaceAssistant())
&& !mSwitchingUser && !isFaceDisabled(user) && becauseCannotSkipBouncer
- && !mKeyguardGoingAway && mFaceSettingEnabledForUser.get(user) && !mLockIconPressed
+ && !mKeyguardGoingAway && mBiometricEnabledForUser.get(user) && !mLockIconPressed
&& strongAuthAllowsScanning && mIsPrimaryUser
&& !mSecureCameraLaunched;
@@ -2176,7 +2180,7 @@
isFaceDisabled(user),
becauseCannotSkipBouncer,
mKeyguardGoingAway,
- mFaceSettingEnabledForUser.get(user),
+ mBiometricEnabledForUser.get(user),
mLockIconPressed,
strongAuthAllowsScanning,
mIsPrimaryUser,
@@ -3235,6 +3239,7 @@
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
+ pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
if (isUdfpsEnrolled()) {
pw.println(" shouldListenForUdfps=" + shouldListenForUdfps());
pw.println(" bouncerVisible=" + mBouncer);
@@ -3257,7 +3262,7 @@
pw.println(" possible=" + isUnlockWithFacePossible(userId));
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
- pw.println(" enabledByUser=" + mFaceSettingEnabledForUser.get(userId));
+ pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
pw.println(" mSecureCameraLaunched=" + mSecureCameraLaunched);
}
if (mFaceListenModels != null && !mFaceListenModels.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
index 2661d89..b544599 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java
@@ -60,17 +60,31 @@
/**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
- * @return List of active AppOps information
+ * @return List of active AppOps information, without paused elements.
*/
List<AppOpItem> getActiveAppOps();
/**
+ * Returns a copy of the list containing all the active AppOps that the controller tracks.
+ *
+ * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
+ * @return List of active AppOps information
+ */
+ List<AppOpItem> getActiveAppOps(boolean showPaused);
+
+ /**
* Returns a copy of the list containing all the active AppOps that the controller tracks, for
* a given user id.
*
* @param userId User id to track
+ * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise.
*
* @return List of active AppOps information for that user id
*/
- List<AppOpItem> getActiveAppOpsForUser(int userId);
+ List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused);
+
+ /**
+ * @return whether this controller is considering the microphone as muted.
+ */
+ boolean isMicMuted();
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 994401d..534f93e 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -241,9 +241,9 @@
AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
if (item == null && active) {
item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime());
- if (code == AppOpsManager.OP_RECORD_AUDIO) {
+ if (isOpMicrophone(code)) {
item.setDisabled(isAnyRecordingPausedLocked(uid));
- } else if (code == AppOpsManager.OP_CAMERA) {
+ } else if (isOpCamera(code)) {
item.setDisabled(mCameraDisabled);
}
mActiveItems.add(item);
@@ -298,6 +298,11 @@
return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName);
}
+ @WorkerThread
+ public List<AppOpItem> getActiveAppOps() {
+ return getActiveAppOps(false);
+ }
+
/**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
@@ -306,8 +311,8 @@
* @return List of active AppOps information
*/
@WorkerThread
- public List<AppOpItem> getActiveAppOps() {
- return getActiveAppOpsForUser(UserHandle.USER_ALL);
+ public List<AppOpItem> getActiveAppOps(boolean showPaused) {
+ return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused);
}
/**
@@ -321,7 +326,7 @@
* @return List of active AppOps information for that user id
*/
@WorkerThread
- public List<AppOpItem> getActiveAppOpsForUser(int userId) {
+ public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) {
Assert.isNotMainThread();
List<AppOpItem> list = new ArrayList<>();
synchronized (mActiveItems) {
@@ -330,7 +335,8 @@
AppOpItem item = mActiveItems.get(i);
if ((userId == UserHandle.USER_ALL
|| UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item.getPackageName()) && !item.isDisabled()) {
+ && isUserVisible(item.getPackageName())
+ && (showPaused || !item.isDisabled())) {
list.add(item);
}
}
@@ -441,9 +447,9 @@
AppOpItem item = mActiveItems.get(i);
boolean paused = false;
- if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) {
+ if (isOpMicrophone(item.getCode())) {
paused = isAnyRecordingPausedLocked(item.getUid());
- } else if (item.getCode() == AppOpsManager.OP_CAMERA) {
+ } else if (isOpCamera(item.getCode())) {
paused = mCameraDisabled;
}
@@ -502,6 +508,19 @@
});
}
+ @Override
+ public boolean isMicMuted() {
+ return mMicMuted;
+ }
+
+ private boolean isOpCamera(int op) {
+ return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
+ }
+
+ private boolean isOpMicrophone(int op) {
+ return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+ }
+
protected class H extends Handler {
H(Looper looper) {
super(looper);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index ed8f32f..0c7b55d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -309,18 +309,14 @@
}
/**
- * Cancel a fingerprint scan.
- *
- * The sensor that triggers an AOD interrupt for fingerprint doesn't give
- * ACTION_UP/ACTION_CANCEL events, so the scan needs to be cancelled manually. This should be
- * called when authentication either succeeds or fails. Failing to cancel the scan will leave
- * the screen in high brightness mode.
+ * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
+ * sensor area even if the user hasn't explicitly lifted their finger yet.
*/
- public void onCancelAodInterrupt() {
+ public void onCancelUdfps() {
if (mUdfpsController == null) {
return;
}
- mUdfpsController.onCancelAodInterrupt();
+ mUdfpsController.onCancelUdfps();
}
private void sendResultAndCleanUp(@DismissedReason int reason,
@@ -499,6 +495,8 @@
if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
mCurrentDialog.onError(errorMessage);
}
+
+ onCancelUdfps();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aa818bf..9239a8a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -35,15 +36,23 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
+import android.media.AudioAttributes;
+import android.os.Handler;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
+import android.view.View;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -94,7 +103,9 @@
@NonNull private final DumpManager mDumpManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final KeyguardViewMediator mKeyguardViewMediator;
- @NonNull private FalsingManager mFalsingManager;
+ @NonNull private final Vibrator mVibrator;
+ @NonNull private final Handler mMainHandler;
+ @NonNull private final FalsingManager mFalsingManager;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps;
@@ -118,6 +129,27 @@
private boolean mIsAodInterruptActive;
@Nullable private Runnable mCancelAodTimeoutAction;
+ private static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .build();
+
+ private final VibrationEffect mEffectTick = VibrationEffect.get(VibrationEffect.EFFECT_TICK);
+ private final VibrationEffect mEffectTextureTick =
+ VibrationEffect.get(VibrationEffect.EFFECT_TEXTURE_TICK);
+ private final VibrationEffect mEffectClick = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+ private final VibrationEffect mEffectHeavy =
+ VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK);
+ private final Runnable mAcquiredVibration = new Runnable() {
+ @Override
+ public void run() {
+ String effect = Settings.Global.getString(mContext.getContentResolver(),
+ "udfps_acquired_type");
+ mVibrator.vibrate(getVibration(effect, mEffectTick), VIBRATION_SONIFICATION_ATTRIBUTES);
+ }
+ };
+
/**
* Keeps track of state within a single FingerprintService request. Note that this state
* persists across configuration changes, etc, since it is considered a single request.
@@ -227,7 +259,9 @@
};
@SuppressLint("ClickableViewAccessibility")
- private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) -> {
+ private final UdfpsView.OnTouchListener mOnTouchListener = this::onTouch;
+
+ private boolean onTouch(View view, MotionEvent event) {
UdfpsView udfpsView = (UdfpsView) view;
final boolean isFingerDown = udfpsView.isIlluminationRequested();
boolean handled = false;
@@ -251,6 +285,27 @@
// data for many other pointers because of multi-touch support.
mActivePointerId = event.getPointerId(0);
mVelocityTracker.addMovement(event);
+
+ // TODO: (b/185124905) these settings are for ux testing purposes and should
+ // be removed (or cached) before going into production
+ final ContentResolver contentResolver = mContext.getContentResolver();
+ int startEnabled = Settings.Global.getInt(contentResolver,
+ "udfps_start", 0);
+ if (startEnabled > 0) {
+ String startEffectSetting = Settings.Global.getString(contentResolver,
+ "udfps_start_type");
+ mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
+ VIBRATION_SONIFICATION_ATTRIBUTES);
+ }
+
+ int acquiredEnabled = Settings.Global.getInt(contentResolver,
+ "udfps_acquired", 0);
+ if (acquiredEnabled > 0) {
+ int delay = Settings.Global.getInt(contentResolver,
+ "udfps_acquired_delay", 500);
+ mMainHandler.removeCallbacks(mAcquiredVibration);
+ mMainHandler.postDelayed(mAcquiredVibration, delay);
+ }
handled = true;
}
break;
@@ -307,7 +362,7 @@
// Do nothing.
}
return handled;
- };
+ }
@Inject
public UdfpsController(@NonNull Context context,
@@ -324,6 +379,9 @@
@NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull FalsingManager falsingManager) {
mContext = context;
+ // TODO (b/185124905): inject main handler and vibrator once done prototyping
+ mMainHandler = new Handler(Looper.getMainLooper());
+ mVibrator = context.getSystemService(Vibrator.class);
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
@@ -559,19 +617,25 @@
// Since the sensor that triggers the AOD interrupt doesn't provide ACTION_UP/ACTION_CANCEL,
// we need to be careful about not letting the screen accidentally remain in high brightness
// mode. As a mitigation, queue a call to cancel the fingerprint scan.
- mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
+ mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps,
AOD_INTERRUPT_TIMEOUT_MILLIS);
// using a hard-coded value for major and minor until it is available from the sensor
onFingerDown(screenX, screenY, minor, major);
}
/**
- * Cancel fingerprint scan.
+ * Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
+ * user explicitly lifts their finger. Generally, this should be called whenever udfps fails
+ * or errors.
*
- * This is intended to be called after the fingerprint scan triggered by the AOD interrupt
- * either succeeds or fails.
+ * The sensor that triggers an AOD fingerprint interrupt (see onAodInterrupt) doesn't give
+ * ACTION_UP/ACTION_CANCEL events, so and AOD interrupt scan needs to be cancelled manually.
+ * This should be called when authentication either succeeds or fails. Failing to cancel the
+ * scan will leave the screen in high brightness mode and will show the HbmSurfaceView until
+ * the user lifts their finger.
*/
- void onCancelAodInterrupt() {
+ void onCancelUdfps() {
+ onFingerUp();
if (!mIsAodInterruptActive) {
return;
}
@@ -580,7 +644,6 @@
mCancelAodTimeoutAction = null;
}
mIsAodInterruptActive = false;
- onFingerUp();
}
// This method can be called from the UI thread.
@@ -598,6 +661,7 @@
// This method can be called from the UI thread.
private void onFingerUp() {
+ mMainHandler.removeCallbacks(mAcquiredVibration);
if (mView == null) {
Log.w(TAG, "Null view in onFingerUp");
return;
@@ -617,4 +681,23 @@
// Do nothing. This method can be implemented for devices that require the high-brightness
// mode for fingerprint illumination.
}
+
+ private VibrationEffect getVibration(String effect, VibrationEffect defaultEffect) {
+ if (TextUtils.isEmpty(effect)) {
+ return defaultEffect;
+ }
+
+ switch (effect.toLowerCase()) {
+ case "click":
+ return mEffectClick;
+ case "heavy":
+ return mEffectHeavy;
+ case "texture_tick":
+ return mEffectTextureTick;
+ case "tick":
+ return mEffectTick;
+ default:
+ return defaultEffect;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index ce5795c..767d7ab 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -35,12 +35,10 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.plugins.GlobalActions;
-import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -50,18 +48,17 @@
public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
private final Context mContext;
- private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
+ private final Lazy<GlobalActionsDialogLite> mGlobalActionsDialogLazy;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
- private final ExtensionController.Extension<GlobalActionsPanelPlugin> mWalletPluginProvider;
private final BlurUtils mBlurUtils;
private final CommandQueue mCommandQueue;
- private GlobalActionsDialog mGlobalActionsDialog;
+ private GlobalActionsDialogLite mGlobalActionsDialog;
private boolean mDisabled;
@Inject
public GlobalActionsImpl(Context context, CommandQueue commandQueue,
- Lazy<GlobalActionsDialog> globalActionsDialogLazy, BlurUtils blurUtils) {
+ Lazy<GlobalActionsDialogLite> globalActionsDialogLazy, BlurUtils blurUtils) {
mContext = context;
mGlobalActionsDialogLazy = globalActionsDialogLazy;
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
@@ -69,10 +66,6 @@
mCommandQueue = commandQueue;
mBlurUtils = blurUtils;
mCommandQueue.addCallback(this);
- mWalletPluginProvider = Dependency.get(ExtensionController.class)
- .newExtension(GlobalActionsPanelPlugin.class)
- .withPlugin(GlobalActionsPanelPlugin.class)
- .build();
}
@Override
@@ -89,8 +82,7 @@
if (mDisabled) return;
mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
- mDeviceProvisionedController.isDeviceProvisioned(),
- mWalletPluginProvider.get());
+ mDeviceProvisionedController.isDeviceProvisioned());
Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 6d1109e..3544f60 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -17,14 +17,18 @@
package com.android.systemui.navigationbar;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
@@ -80,6 +84,8 @@
ConfigurationController.ConfigurationListener,
NavigationModeController.ModeChangedListener, Dumpable {
+ private static final float TABLET_MIN_DPS = 600;
+
private static final String TAG = NavigationBarController.class.getSimpleName();
private final Context mContext;
@@ -107,6 +113,8 @@
private final Handler mHandler;
private final DisplayManager mDisplayManager;
private final NavigationBarOverlayController mNavBarOverlayController;
+ private int mNavMode;
+ private boolean mIsTablet;
/** A displayId - nav bar maps. */
@VisibleForTesting
@@ -172,11 +180,30 @@
configurationController.addCallback(this);
mConfigChanges.applyNewConfig(mContext.getResources());
mNavBarOverlayController = navBarOverlayController;
+ mNavMode = mNavigationModeController.addListener(this);
mNavigationModeController.addListener(this);
}
@Override
public void onConfigChanged(Configuration newConfig) {
+ boolean isOldConfigTablet = mIsTablet;
+ mIsTablet = isTablet(newConfig);
+ boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
+ // If we folded/unfolded while in 3 button, show navbar in folded state, hide in unfolded
+ if (isThreeButtonTaskbarFlagEnabled() &&
+ largeScreenChanged && mNavMode == NAV_BAR_MODE_3BUTTON) {
+ if (!mIsTablet) {
+ // Folded state, show 3 button nav bar
+ createNavigationBar(mContext.getDisplay(), null, null);
+ } else {
+ // Unfolded state, hide 3 button nav bars
+ for (int i = 0; i < mNavigationBars.size(); i++) {
+ removeNavigationBar(mNavigationBars.keyAt(i));
+ }
+ }
+ return;
+ }
+
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
for (int i = 0; i < mNavigationBars.size(); i++) {
recreateNavigationBar(mNavigationBars.keyAt(i));
@@ -190,7 +217,19 @@
@Override
public void onNavigationModeChanged(int mode) {
+ final int oldMode = mNavMode;
+ mNavMode = mode;
mHandler.post(() -> {
+ // create/destroy nav bar based on nav mode only in unfolded state
+ if (isThreeButtonTaskbarFlagEnabled() && oldMode != mNavMode && mIsTablet) {
+ if (oldMode == NAV_BAR_MODE_3BUTTON &&
+ mNavigationBars.get(mContext.getDisplayId()) == null) {
+ // We remove navbar for 3 button unfolded, add it back in
+ createNavigationBar(mContext.getDisplay(), null, null);
+ } else if (mNavMode == NAV_BAR_MODE_3BUTTON) {
+ removeNavigationBar(mContext.getDisplayId());
+ }
+ }
for (int i = 0; i < mNavigationBars.size(); i++) {
NavigationBar navBar = mNavigationBars.valueAt(i);
if (navBar == null) {
@@ -212,6 +251,7 @@
@Override
public void onDisplayReady(int displayId) {
Display display = mDisplayManager.getDisplay(displayId);
+ mIsTablet = isTablet(mContext.getResources().getConfiguration());
createNavigationBar(display, null /* savedState */, null /* result */);
}
@@ -267,6 +307,10 @@
return;
}
+ if (isThreeButtonTaskbarEnabled()) {
+ return;
+ }
+
final int displayId = display.getDisplayId();
final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
@@ -398,6 +442,24 @@
return mNavigationBars.get(DEFAULT_DISPLAY);
}
+ private boolean isThreeButtonTaskbarEnabled() {
+ return mIsTablet && mNavMode == NAV_BAR_MODE_3BUTTON &&
+ isThreeButtonTaskbarFlagEnabled();
+ }
+
+ private boolean isThreeButtonTaskbarFlagEnabled() {
+ return SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
+ }
+
+ private boolean isTablet(Configuration newConfig) {
+ float density = Resources.getSystem().getDisplayMetrics().density;
+ int size = Math.min((int) (density * newConfig.screenWidthDp),
+ (int) (density* newConfig.screenHeightDp));
+ DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
+ float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
+ return (size / densityRatio) >= TABLET_MIN_DPS;
+ }
+
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
for (int i = 0; i < mNavigationBars.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 2ca6a92..01c80f6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -281,7 +281,7 @@
// When in gestural and the IME is showing, don't use the nearest region since it will take
// gesture space away from the IME
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- info.touchableRegion.set(getButtonLocations(false /* includeFloatingRotationButton */,
+ info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
false /* inScreen */, false /* useNearestRegion */));
};
@@ -982,7 +982,7 @@
*/
public void notifyActiveTouchRegions() {
mOverviewProxyService.onActiveNavBarRegionChanges(
- getButtonLocations(true /* includeFloatingRotationButton */, true /* inScreen */,
+ getButtonLocations(true /* includeFloatingButtons */, true /* inScreen */,
true /* useNearestRegion */));
}
@@ -995,14 +995,14 @@
}
/**
- * @param includeFloatingRotationButton Whether to include the floating rotation button in the
- * region for all the buttons
+ * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
+ * the region for all the buttons
* @param inScreenSpace Whether to return values in screen space or window space
* @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
* @return
*/
- private Region getButtonLocations(boolean includeFloatingRotationButton,
- boolean inScreenSpace, boolean useNearestRegion) {
+ private Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
+ boolean useNearestRegion) {
if (useNearestRegion && !inScreenSpace) {
// We currently don't support getting the nearest region in anything but screen space
useNearestRegion = false;
@@ -1014,13 +1014,13 @@
updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion);
updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion);
updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion);
- if (includeFloatingRotationButton && mFloatingRotationButton.isVisible()) {
+ if (includeFloatingButtons && mFloatingRotationButton.isVisible()) {
// Note: this button is floating so the nearest region doesn't apply
updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace);
} else {
updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion);
}
- if (mNavBarOverlayController.isNavigationBarOverlayEnabled()
+ if (includeFloatingButtons && mNavBarOverlayController.isNavigationBarOverlayEnabled()
&& mNavBarOverlayController.isVisible()) {
// Note: this button is floating so the nearest region doesn't apply
updateButtonLocation(mNavBarOverlayController.getCurrentView(), inScreenSpace);
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index f87ea7c..feb27d80 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
+import com.android.systemui.appops.AppOpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -48,7 +49,6 @@
return PrivacyDialog(context, list, starter)
}
}
-
/**
* Controller for [PrivacyDialog].
*
@@ -66,6 +66,7 @@
private val uiExecutor: Executor,
private val privacyLogger: PrivacyLogger,
private val keyguardStateController: KeyguardStateController,
+ private val appOpsController: AppOpsController,
@VisibleForTesting private val dialogProvider: DialogProvider
) {
@@ -79,7 +80,8 @@
@Background backgroundExecutor: Executor,
@Main uiExecutor: Executor,
privacyLogger: PrivacyLogger,
- keyguardStateController: KeyguardStateController
+ keyguardStateController: KeyguardStateController,
+ appOpsController: AppOpsController
) : this(
permissionManager,
packageManager,
@@ -90,6 +92,7 @@
uiExecutor,
privacyLogger,
keyguardStateController,
+ appOpsController,
defaultDialogProvider
)
@@ -127,7 +130,9 @@
}
@WorkerThread
- private fun permGroupUsage(): List<PermGroupUsage> = permissionManager.indicatorAppOpUsageData
+ private fun permGroupUsage(): List<PermGroupUsage> {
+ return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted)
+ }
/**
* Show the [PrivacyDialog]
@@ -261,4 +266,4 @@
starter: (String, Int) -> Unit
): PrivacyDialog
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 4c617ed..63ec6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -54,7 +54,8 @@
data class PrivacyItem(
val privacyType: PrivacyType,
val application: PrivacyApplication,
- val timeStampElapsed: Long = UNKNOWN_TIMESTAMP
+ val timeStampElapsed: Long = UNKNOWN_TIMESTAMP,
+ val paused: Boolean = false
) {
val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " +
"$timeStampElapsed)"
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index f7e2a31..8b27b6e 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -257,7 +257,7 @@
privacyList = emptyList()
return
}
- val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter {
+ val list = appOpsController.getActiveAppOps(true).filter {
UserHandle.getUserId(it.uid) in currentUserIds ||
it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE ||
it.code == AppOpsManager.OP_PHONE_CALL_CAMERA
@@ -279,7 +279,9 @@
// Anything earlier than this timestamp can be removed
val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS
- val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list }
+ val mustKeep = privacyList.filter {
+ it.timeStampElapsed > removeBeforeTime && !(it isIn list)
+ }
// There are items we must keep because they haven't been around for enough time.
if (mustKeep.isNotEmpty()) {
@@ -291,7 +293,18 @@
logger.logPrivacyItemsUpdateScheduled(delay)
holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay)
}
- return list + mustKeep
+ return list.filter { !it.paused } + mustKeep
+ }
+
+ /**
+ * Ignores the paused status to determine if the element is in the list
+ */
+ private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean {
+ return list.any {
+ it.privacyType == privacyType &&
+ it.application == application &&
+ it.timeStampElapsed == timeStampElapsed
+ }
}
private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
@@ -308,7 +321,7 @@
return null
}
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
- return PrivacyItem(type, app, appOpItem.timeStartedElapsed)
+ return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled)
}
interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 32723b4..f7fa5bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -8,7 +8,6 @@
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -71,8 +70,6 @@
private int mMinRows = 1;
private int mMaxColumns = TileLayout.NO_MAX_COLUMNS;
- private final boolean mSideLabels;
-
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context, SCROLL_CUBIC);
@@ -83,14 +80,9 @@
mLayoutDirection = getLayoutDirection();
mClippingRect = new Rect();
- TypedArray t = context.getTheme().obtainStyledAttributes(
- attrs, R.styleable.PagedTileLayout, 0, 0);
- mSideLabels = t.getBoolean(R.styleable.PagedTileLayout_sideLabels, false);
- t.recycle();
- if (mSideLabels) {
- setPageMargin(context.getResources().getDimensionPixelOffset(
+ // Make sure there's a space between pages when scroling
+ setPageMargin(context.getResources().getDimensionPixelOffset(
R.dimen.qs_tile_margin_horizontal));
- }
}
private int mLastMaxHeight = -1;
@@ -228,8 +220,7 @@
private TileLayout createTileLayout() {
TileLayout page = (TileLayout) LayoutInflater.from(getContext())
- .inflate(mSideLabels ? R.layout.qs_paged_page_side_labels
- : R.layout.qs_paged_page, this, false);
+ .inflate(R.layout.qs_paged_page, this, false);
page.setMinRows(mMinRows);
page.setMaxColumns(mMaxColumns);
return page;
@@ -345,9 +336,8 @@
// Update bottom padding, useful for removing extra space once the panel page indicator is
// hidden.
Resources res = getContext().getResources();
- if (mSideLabels) {
- setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
- }
+ setPageMargin(res.getDimensionPixelOffset(R.dimen.qs_tile_margin_horizontal));
+
setPadding(0, 0, 0,
getContext().getResources().getDimensionPixelSize(
R.dimen.qs_paged_tile_layout_padding_bottom));
@@ -550,18 +540,6 @@
}
};
- public static class TilePage extends TileLayout {
-
- public TilePage(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public boolean isFull() {
- return mRecords.size() >= maxTiles();
- }
-
- }
-
private final PagerAdapter mAdapter = new PagerAdapter() {
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index ea471b9..cefcd4a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -33,7 +33,6 @@
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.tileimpl.HeightOverrideable;
import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -102,14 +101,13 @@
private final Executor mExecutor;
private final TunerService mTunerService;
private boolean mShowCollapsedOnKeyguard;
- private final FeatureFlags mFeatureFlags;
@Inject
public QSAnimator(QS qs, QuickQSPanel quickPanel, QuickStatusBarHeader quickStatusBarHeader,
QSPanelController qsPanelController,
QuickQSPanelController quickQSPanelController, QSTileHost qsTileHost,
QSSecurityFooter securityFooter, @Main Executor executor, TunerService tunerService,
- FeatureFlags featureFlags, QSExpansionPathInterpolator qsExpansionPathInterpolator) {
+ QSExpansionPathInterpolator qsExpansionPathInterpolator) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanelController = qsPanelController;
@@ -119,7 +117,6 @@
mHost = qsTileHost;
mExecutor = executor;
mTunerService = tunerService;
- mFeatureFlags = featureFlags;
mQSExpansionPathInterpolator = qsExpansionPathInterpolator;
mHost.addCallback(this);
mQsPanelController.addOnAttachStateChangeListener(this);
@@ -247,7 +244,6 @@
+ mQs.getHeader().getPaddingBottom();
firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
- boolean qsSideLabelsEnabled = mFeatureFlags.isQSLabelsEnabled();
int qqsTileHeight = 0;
if (mQsPanelController.areThereTiles()) {
@@ -275,22 +271,19 @@
if (count < tileLayout.getNumVisibleTiles()) {
getRelativePosition(loc1, quickTileView, view);
getRelativePosition(loc2, tileView, view);
- int yOffset = qsSideLabelsEnabled
- ? loc2[1] - loc1[1]
- : mQuickStatusBarHeader.getOffsetTranslation();
+ int yOffset = loc2[1] - loc1[1];
// Move the quick tile right from its location to the new one.
- View v = qsSideLabelsEnabled ? quickTileView.getIcon() : quickTileView;
+ View v = quickTileView.getIcon();
translationXBuilder.addFloat(v, "translationX", 0, xDiff);
translationYBuilder.addFloat(v, "translationY", 0, yDiff - yOffset);
mAllViews.add(v);
// Move the real tile from the quick tile position to its final
// location.
- v = qsSideLabelsEnabled ? tileIcon : tileView;
+ v = tileIcon;
translationXBuilder.addFloat(v, "translationX", -xDiff, 0);
translationYBuilder.addFloat(v, "translationY", -yDiff + yOffset, 0);
- if (qsSideLabelsEnabled) {
// Offset the translation animation on the views
// (that goes from 0 to getOffsetTranslation)
int offsetWithQSBHTranslation =
@@ -300,28 +293,24 @@
translationYBuilder.addFloat(tileView, "translationY",
-offsetWithQSBHTranslation, 0);
- if (mQQSTileHeightAnimator == null) {
- mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
- quickTileView.getHeight(), tileView.getHeight());
- qqsTileHeight = quickTileView.getHeight();
- }
-
- mQQSTileHeightAnimator.addView(quickTileView);
- View qqsLabelContainer = quickTileView.getLabelContainer();
- View qsLabelContainer = tileView.getLabelContainer();
-
- getRelativePosition(loc1, qqsLabelContainer, view);
- getRelativePosition(loc2, qsLabelContainer, view);
- yDiff = loc2[1] - loc1[1] - yOffset;
-
- translationYBuilder.addFloat(qqsLabelContainer, "translationY", 0,
- yDiff);
- translationYBuilder.addFloat(qsLabelContainer, "translationY", -yDiff,
- 0);
- mAllViews.add(qqsLabelContainer);
- mAllViews.add(qsLabelContainer);
+ if (mQQSTileHeightAnimator == null) {
+ mQQSTileHeightAnimator = new HeightExpansionAnimator(this,
+ quickTileView.getHeight(), tileView.getHeight());
+ qqsTileHeight = quickTileView.getHeight();
}
+ mQQSTileHeightAnimator.addView(quickTileView);
+ View qqsLabelContainer = quickTileView.getLabelContainer();
+ View qsLabelContainer = tileView.getLabelContainer();
+
+ getRelativePosition(loc1, qqsLabelContainer, view);
+ getRelativePosition(loc2, qsLabelContainer, view);
+ yDiff = loc2[1] - loc1[1] - yOffset;
+
+ translationYBuilder.addFloat(qqsLabelContainer, "translationY", 0, yDiff);
+ translationYBuilder.addFloat(qsLabelContainer, "translationY", -yDiff, 0);
+ mAllViews.add(qqsLabelContainer);
+ mAllViews.add(qsLabelContainer);
} else { // These tiles disappear when expanding
firstPageBuilder.addFloat(quickTileView, "alpha", 1, 0);
translationYBuilder.addFloat(quickTileView, "translationY", 0, yDiff);
@@ -333,11 +322,7 @@
translationX);
}
- if (qsSideLabelsEnabled) {
- mQuickQsViews.add(tileView);
- } else {
- mQuickQsViews.add(tileView.getIconWithBackground());
- }
+ mQuickQsViews.add(tileView);
mAllViews.add(tileView.getIcon());
mAllViews.add(quickTileView);
} else if (mFullRows && isIconInAnimatedRow(count)) {
@@ -346,27 +331,22 @@
mAllViews.add(tileIcon);
} else {
- if (!qsSideLabelsEnabled) {
- firstPageBuilder.addFloat(tileView, "alpha", 0, 1);
- firstPageBuilder.addFloat(tileView, "translationY", -heightDiff, 0);
- } else {
- // Pretend there's a corresponding QQS tile (for the position) that we are
- // expanding from.
- SideLabelTileLayout qqsLayout =
- (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
- getRelativePosition(loc1, qqsLayout, view);
- getRelativePosition(loc2, tileView, view);
- int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count));
- translationYBuilder.addFloat(tileView, "translationY", -diff, 0);
- if (mOtherTilesExpandAnimator == null) {
- mOtherTilesExpandAnimator =
- new HeightExpansionAnimator(
- this, qqsTileHeight, tileView.getHeight());
- }
- mOtherTilesExpandAnimator.addView(tileView);
- tileView.setClipChildren(true);
- tileView.setClipToPadding(true);
+ // Pretend there's a corresponding QQS tile (for the position) that we are
+ // expanding from.
+ SideLabelTileLayout qqsLayout =
+ (SideLabelTileLayout) mQuickQsPanel.getTileLayout();
+ getRelativePosition(loc1, qqsLayout, view);
+ getRelativePosition(loc2, tileView, view);
+ int diff = loc2[1] - (loc1[1] + qqsLayout.getPhantomTopPosition(count));
+ translationYBuilder.addFloat(tileView, "translationY", -diff, 0);
+ if (mOtherTilesExpandAnimator == null) {
+ mOtherTilesExpandAnimator =
+ new HeightExpansionAnimator(
+ this, qqsTileHeight, tileView.getHeight());
}
+ mOtherTilesExpandAnimator.addView(tileView);
+ tileView.setClipChildren(true);
+ tileView.setClipToPadding(true);
}
mAllViews.add(tileView);
@@ -392,7 +372,6 @@
.build();
// Fade in the tiles/labels as we reach the final position.
Builder builder = new Builder()
- .setStartDelay(qsSideLabelsEnabled ? 0 : EXPANDED_TILE_DELAY)
.addFloat(tileLayout, "alpha", 0, 1);
mFirstPageDelayedAnimator = builder.build();
@@ -470,12 +449,7 @@
// Returns true if the view is a possible page in PagedTileLayout
private boolean isAPage(View view) {
- if (view instanceof PagedTileLayout.TilePage) {
- return true;
- } else if (view instanceof SideLabelTileLayout) {
- return !(view instanceof QuickQSPanel.QQSSideLabelTileLayout);
- }
- return false;
+ return view.getClass().equals(SideLabelTileLayout.class);
}
public void setPosition(float position) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 586176f..bf9acc2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -22,7 +22,6 @@
import android.content.res.Configuration;
import android.graphics.Point;
import android.util.AttributeSet;
-import android.util.Pair;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
@@ -61,7 +60,6 @@
private QuickStatusBarHeader mHeader;
private float mQsExpansion;
private QSCustomizer mQSCustomizer;
- private View mDragHandle;
private NonInterceptingScrollView mQSPanelContainer;
private View mBackground;
@@ -84,7 +82,6 @@
mQSDetail = findViewById(R.id.qs_detail);
mHeader = findViewById(R.id.header);
mQSCustomizer = findViewById(R.id.qs_customize);
- mDragHandle = findViewById(R.id.qs_drag_handle_view);
mBackground = findViewById(R.id.quick_settings_background);
mHeader.getHeaderQsPanel().setMediaVisibilityChangedListener((visible) -> {
if (mHeader.getHeaderQsPanel().isShown()) {
@@ -240,8 +237,6 @@
int scrollBottom = calculateContainerBottom();
setBottom(getTop() + height);
mQSDetail.setBottom(getTop() + scrollBottom);
- // Pin the drag handle to the bottom of the panel.
- mDragHandle.setTranslationY(scrollBottom - mDragHandle.getHeight());
mBackground.setTop(mQSPanelContainer.getTop());
updateBackgroundBottom(scrollBottom, animate);
}
@@ -278,7 +273,6 @@
public void setExpansion(float expansion) {
mQsExpansion = expansion;
- mDragHandle.setAlpha(1.0f - expansion);
updateExpansion();
}
@@ -296,9 +290,6 @@
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
qsPanelController.setContentMargins(mContentPadding, mContentPadding);
- Pair<Integer, Integer> margins = qsPanelController.getVisualSideMargins();
- // Apply paddings based on QSPanel
- mQSCustomizer.setContentPaddings(margins.first, margins.second);
} else if (view == mHeader) {
// The header contains the QQS panel which needs to have special padding, to
// visually align them.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index eb7b115..3718713 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -71,7 +71,6 @@
private float mExpansionAmount;
protected View mEdit;
- protected View mEditContainer;
private TouchAnimator mSettingsCogAnimator;
private View mActionsContainer;
@@ -107,7 +106,6 @@
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
mActionsContainer = requireViewById(R.id.qs_footer_actions_container);
- mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
mTunerIcon = requireViewById(R.id.tuner_icon);
@@ -185,9 +183,6 @@
.addFloat(mPageIndicator, "alpha", 0, 1)
.addFloat(mBuildText, "alpha", 0, 1)
.setStartDelay(0.9f);
- if (mEditContainer != null) {
- builder.addFloat(mEditContainer, "alpha", 0, 1);
- }
return builder.build();
}
@@ -283,9 +278,6 @@
mTunerIcon.setVisibility(isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.GONE);
- if (mEditContainer != null) {
- mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
- }
mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 27cc268..f89e70a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -27,12 +27,10 @@
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
-import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewStub;
import android.widget.LinearLayout;
import com.android.internal.logging.UiEventLogger;
@@ -72,8 +70,6 @@
private final H mHandler = new H();
/** Whether or not the QS media player feature is enabled. */
protected boolean mUsingMediaPlayer;
- private int mVisualMarginStart;
- private int mVisualMarginEnd;
protected boolean mExpanded;
protected boolean mListening;
@@ -96,7 +92,6 @@
private PageIndicator mFooterPageIndicator;
private int mContentMarginStart;
private int mContentMarginEnd;
- private int mVisualTilePadding;
private boolean mUsingHorizontalLayout;
private Record mDetailRecord;
@@ -111,9 +106,7 @@
protected QSTileLayout mTileLayout;
private int mLastOrientation = -1;
private int mMediaTotalBottomMargin;
- private int mFooterMarginStartHorizontal;
private Consumer<Boolean> mMediaVisibilityChangedListener;
- protected boolean mSideLabels;
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,21 +121,7 @@
}
- protected void inflateQSFooter(boolean newFooter) {
- ViewStub stub = findViewById(R.id.qs_footer_stub);
- if (stub != null) {
- stub.setLayoutResource(
- newFooter ? R.layout.qs_footer_impl_two_lines : R.layout.qs_footer_impl);
- stub.inflate();
- mFooter = findViewById(R.id.qs_footer);
- }
- }
-
- void initialize(boolean sideLabels) {
- mSideLabels = sideLabels;
-
- inflateQSFooter(sideLabels);
-
+ void initialize() {
mRegularTileLayout = createRegularTileLayout();
mTileLayout = mRegularTileLayout;
@@ -195,8 +174,7 @@
public QSTileLayout createRegularTileLayout() {
if (mRegularTileLayout == null) {
mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext)
- .inflate(mSideLabels ? R.layout.qs_paged_tile_layout_side_labels
- : R.layout.qs_paged_tile_layout, this, false);
+ .inflate(R.layout.qs_paged_tile_layout, this, false);
}
return mRegularTileLayout;
}
@@ -311,11 +289,6 @@
}
public void updateResources() {
- int tileSize = getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size);
- int tileBg = getResources().getDimensionPixelSize(R.dimen.qs_tile_background_size);
- mFooterMarginStartHorizontal = getResources().getDimensionPixelSize(
- R.dimen.qs_footer_horizontal_margin);
- mVisualTilePadding = mSideLabels ? 0 : (int) ((tileSize - tileBg) / 2.0f);
updatePadding();
updatePageIndicator();
@@ -358,6 +331,7 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ mFooter = findViewById(R.id.qs_footer);
mDivider = findViewById(R.id.divider);
}
@@ -638,60 +612,10 @@
// to the edge like the brightness slider
mContentMarginStart = startMargin;
mContentMarginEnd = endMargin;
- updateTileLayoutMargins(mContentMarginStart - mVisualTilePadding,
- mContentMarginEnd - mVisualTilePadding);
updateMediaHostContentMargins(mediaHostView);
- updateFooterMargin();
updateDividerMargin();
}
- private void updateFooterMargin() {
- if (mFooter != null) {
- int footerMargin = 0;
- int indicatorMargin = 0;
- if (mUsingHorizontalLayout && !mSideLabels) {
- footerMargin = mFooterMarginStartHorizontal;
- indicatorMargin = footerMargin - mVisualMarginEnd;
- }
- updateMargins(mFooter, footerMargin, 0);
- // The page indicator isn't centered anymore because of the visual positioning.
- // Let's fix it by adding some margin
- if (mFooterPageIndicator != null) {
- updateMargins(mFooterPageIndicator, 0, indicatorMargin);
- }
- }
- }
-
- /**
- * Update the margins of all tile Layouts.
- *
- * @param visualMarginStart the visual start margin of the tile, adjusted for local insets
- * to the tile. This can be set on a tileLayout
- * @param visualMarginEnd the visual end margin of the tile, adjusted for local insets
- * to the tile. This can be set on a tileLayout
- */
- private void updateTileLayoutMargins(int visualMarginStart, int visualMarginEnd) {
- mVisualMarginStart = visualMarginStart;
- mVisualMarginEnd = visualMarginEnd;
- updateTileLayoutMargins();
- }
-
- public Pair<Integer, Integer> getVisualSideMargins() {
- if (mSideLabels) {
- return new Pair(0, 0);
- } else {
- return new Pair(mVisualMarginStart, mUsingHorizontalLayout ? 0 : mVisualMarginEnd);
- }
- }
-
- private void updateTileLayoutMargins() {
- int marginEnd = mVisualMarginEnd;
- if (mUsingHorizontalLayout || mSideLabels) {
- marginEnd = 0;
- }
- updateMargins((View) mTileLayout, mSideLabels ? 0 : mVisualMarginStart, marginEnd);
- }
-
private void updateDividerMargin() {
if (mDivider == null) return;
updateMargins(mDivider, mContentMarginStart, mContentMarginEnd);
@@ -769,22 +693,13 @@
newLayout.setListening(mListening, uiEventLogger);
if (needsDynamicRowsAndColumns()) {
newLayout.setMinRows(horizontal ? 2 : 1);
- // Let's use 3 columns to match the current layout
- int columns;
- if (mSideLabels) {
- columns = horizontal ? 2 : 4;
- } else {
- columns = horizontal ? 3 : TileLayout.NO_MAX_COLUMNS;
- }
- newLayout.setMaxColumns(columns);
+ newLayout.setMaxColumns(horizontal ? 2 : 4);
}
updateMargins(mediaHostView);
}
}
private void updateMargins(ViewGroup mediaHostView) {
- updateTileLayoutMargins();
- updateFooterMargin();
updateDividerMargin();
updateMediaHostContentMargins(mediaHostView);
updateHorizontalLinearLayoutMargins();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index eda1abb..5b6b5df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -22,7 +22,6 @@
import android.annotation.NonNull;
import android.content.res.Configuration;
-import android.util.Pair;
import android.view.View;
import android.view.ViewGroup;
@@ -291,11 +290,6 @@
}
/** */
- public Pair<Integer, Integer> getVisualSideMargins() {
- return mView.getVisualSideMargins();
- }
-
- /** */
public void showDetailAdapter(DetailAdapter detailAdapter, int x, int y) {
mView.showDetailAdapter(true, detailAdapter, new int[]{x, y});
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index e41a038..925c9eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -75,8 +75,6 @@
private float mRevealExpansion;
private final QSHost.Callback mQSHostCallback = this::setTiles;
- protected boolean mShowLabels = true;
- protected boolean mQSLabelFlag;
private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@@ -121,14 +119,13 @@
mQSLogger = qsLogger;
mDumpManager = dumpManager;
mFeatureFlags = featureFlags;
- mQSLabelFlag = featureFlags.isQSLabelsEnabled();
mShouldUseSplitNotificationShade =
Utils.shouldUseSplitNotificationShade(mFeatureFlags, getResources());
}
@Override
protected void onInit() {
- mView.initialize(mQSLabelFlag);
+ mView.initialize();
mQSLogger.logAllTilesChangeListening(mView.isListening(), mView.getDumpableTag(), "");
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index e7828c3..63733b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -50,16 +50,11 @@
}
@Override
- void initialize(boolean sideLabels) {
- super.initialize(sideLabels);
+ void initialize() {
+ super.initialize();
applyBottomMargin((View) mRegularTileLayout);
}
- @Override
- protected void inflateQSFooter(boolean newFooter) {
- // No footer
- }
-
private void applyBottomMargin(View view) {
int margin = getResources().getDimensionPixelSize(R.dimen.qs_header_tile_margin_bottom);
MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
@@ -74,22 +69,14 @@
@Override
public TileLayout createRegularTileLayout() {
- if (mSideLabels) {
- return new QQSSideLabelTileLayout(mContext);
- } else {
- return new QuickQSPanel.HeaderTileLayout(mContext);
- }
+ return new QQSSideLabelTileLayout(mContext);
}
@Override
protected QSTileLayout createHorizontalTileLayout() {
- if (mSideLabels) {
- TileLayout t = createRegularTileLayout();
- t.setMaxColumns(2);
- return t;
- } else {
- return new DoubleLineTileLayout(mContext);
- }
+ TileLayout t = createRegularTileLayout();
+ t.setMaxColumns(2);
+ return t;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 30a08c6..7518b20 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -141,19 +141,6 @@
}
}
- /**
- * Sets the padding for the RecyclerView. Also, updates the margin between the tiles in the
- * {@link TileAdapter}.
- */
- public void setContentPaddings(int paddingStart, int paddingEnd) {
- mRecyclerView.setPaddingRelative(
- paddingStart,
- mRecyclerView.getPaddingTop(),
- paddingEnd,
- mRecyclerView.getPaddingBottom()
- );
- }
-
/** Hide the customizer. */
public void hide(boolean animate) {
if (isShown) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index 006b230..5080533 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -14,8 +14,6 @@
package com.android.systemui.qs.customize;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
@@ -61,7 +59,6 @@
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Named;
/** */
@QSScope
@@ -110,25 +107,21 @@
private final AccessibilityDelegateCompat mAccessibilityDelegate;
private RecyclerView mRecyclerView;
private int mNumColumns;
- private final boolean mUseHorizontalTiles;
@Inject
public TileAdapter(
@QSThemedContext Context context,
QSTileHost qsHost,
- UiEventLogger uiEventLogger,
- @Named(QS_LABELS_FLAG) boolean useHorizontalTiles
- ) {
+ UiEventLogger uiEventLogger) {
mContext = context;
mHost = qsHost;
mUiEventLogger = uiEventLogger;
mItemTouchHelper = new ItemTouchHelper(mCallbacks);
mDecoration = new TileItemDecoration(context);
- mMarginDecoration = new MarginTileDecoration(!useHorizontalTiles);
+ mMarginDecoration = new MarginTileDecoration();
mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles);
mNumColumns = context.getResources().getInteger(NUM_COLUMNS_ID);
mAccessibilityDelegate = new TileAdapterDelegate();
- mUseHorizontalTiles = useHorizontalTiles;
mSizeLookup.setSpanIndexCacheEnabled(true);
}
@@ -287,9 +280,7 @@
}
FrameLayout frame = (FrameLayout) inflater.inflate(R.layout.qs_customize_tile_frame, parent,
false);
- View view = mUseHorizontalTiles
- ? new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context))
- : new CustomizeTileView(context, new QSIconViewImpl(context));
+ View view = new CustomizeTileViewHorizontal(context, new QSIconViewImpl(context));
frame.addView(view);
return new Holder(frame);
}
@@ -715,11 +706,6 @@
private static class MarginTileDecoration extends ItemDecoration {
private int mHalfMargin;
- private final boolean mUseOutsideMargins;
-
- private MarginTileDecoration(boolean useOutsideMargins) {
- mUseOutsideMargins = useOutsideMargins;
- }
public void setHalfMargin(int halfMargin) {
mHalfMargin = halfMargin;
@@ -738,9 +724,9 @@
if (view instanceof TextView) {
super.getItemOffsets(outRect, view, parent, state);
} else {
- if (mUseOutsideMargins || (column != 0 && column != lm.getSpanCount() - 1)) {
- // Using outside margins or in a column that's not leftmost or rightmost
- // (half of the margin between columns).
+ if (column != 0 && column != lm.getSpanCount() - 1) {
+ // In a column that's not leftmost or rightmost (half of the margin between
+ // columns).
outRect.left = mHalfMargin;
outRect.right = mHalfMargin;
} else if (column == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
index 10192bc..a1e1d64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFlagsModule.java
@@ -30,18 +30,11 @@
@Module
public interface QSFlagsModule {
- String QS_LABELS_FLAG = "qs_labels_flag";
+
String RBC_AVAILABLE = "rbc_available";
String PM_LITE_ENABLED = "pm_lite";
String PM_LITE_SETTING = "sysui_pm_lite";
- @Provides
- @SysUISingleton
- @Named(QS_LABELS_FLAG)
- static boolean provideQSFlag(FeatureFlags featureFlags) {
- return featureFlags.isQSLabelsEnabled();
- }
-
/** */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 9b0536c..3437dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -14,8 +14,6 @@
package com.android.systemui.qs.tileimpl;
-import static com.android.systemui.qs.dagger.QSFlagsModule.QS_LABELS_FLAG;
-
import android.content.Context;
import android.os.Build;
import android.util.Log;
@@ -56,7 +54,6 @@
import com.android.systemui.util.leak.GarbageMonitor;
import javax.inject.Inject;
-import javax.inject.Named;
import javax.inject.Provider;
import dagger.Lazy;
@@ -97,12 +94,9 @@
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
- private final boolean mSideLabels;
-
@Inject
public QSFactoryImpl(
Lazy<QSHost> qsHostLazy,
- @Named(QS_LABELS_FLAG) boolean useSideLabels,
Provider<CustomTile.Builder> customTileBuilderProvider,
Provider<WifiTile> wifiTileProvider,
Provider<InternetTile> internetTileProvider,
@@ -134,8 +128,6 @@
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
- mSideLabels = useSideLabels;
-
mWifiTileProvider = wifiTileProvider;
mInternetTileProvider = internetTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
@@ -251,12 +243,6 @@
@Override
public QSTileView createTileView(Context context, QSTile tile, boolean collapsedView) {
QSIconView icon = tile.createTileView(context);
- if (mSideLabels) {
- return new QSTileViewHorizontal(context, icon, collapsedView);
- } else if (collapsedView) {
- return new QSTileBaseView(context, icon, collapsedView);
- } else {
- return new com.android.systemui.qs.tileimpl.QSTileView(context, icon);
- }
+ return new QSTileViewHorizontal(context, icon, collapsedView);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index aa8ce85..ba69dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,9 +66,9 @@
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.PagedTileLayout.TilePage;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.logging.QSLogger;
import java.io.FileDescriptor;
@@ -497,7 +497,7 @@
private void updateIsFullQs() {
for (Object listener : mListeners) {
- if (TilePage.class.equals(listener.getClass())) {
+ if (SideLabelTileLayout.class.equals(listener.getClass())) {
mIsFullQs = 1;
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index ec3a857..17b489c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -53,11 +53,6 @@
return mFlagReader.isEnabled(R.bool.flag_notification_twocolumn);
}
- // Does not support runtime changes
- public boolean isQSLabelsEnabled() {
- return mFlagReader.isEnabled(R.bool.flag_qs_labels);
- }
-
public boolean isKeyguardLayoutEnabled() {
return mFlagReader.isEnabled(R.bool.flag_keyguard_layout);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index f57fd21..f4266a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -41,6 +41,7 @@
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -82,6 +83,9 @@
private Rect mClipRect = new Rect();
private int mCutoutHeight;
private int mGapHeight;
+ private int mIndexOfFirstViewInShelf = -1;
+ private int mIndexOfFirstViewInOverflowingSection = -1;
+
private NotificationShelfController mController;
public NotificationShelf(Context context, AttributeSet attrs) {
@@ -159,30 +163,49 @@
}
/** Update the state of the shelf. */
- public void updateState(AmbientState ambientState) {
+ public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
ShelfState viewState = (ShelfState) getViewState();
if (mShowNotificationShelf && lastView != null) {
- float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
- + ambientState.getStackTranslation();
ExpandableViewState lastViewState = lastView.getViewState();
- float viewEnd = lastViewState.yTranslation + lastViewState.height;
viewState.copyFrom(lastViewState);
+
viewState.height = getIntrinsicHeight();
- viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height,
- getFullyClosedTranslation());
viewState.zTranslation = ambientState.getBaseZHeight();
viewState.clipTopAmount = 0;
viewState.alpha = 1f - ambientState.getHideAmount();
viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
viewState.xTranslation = getTranslationX();
+ viewState.hasItemsInStableShelf = lastViewState.inShelf;
+ viewState.firstViewInShelf = algorithmState.firstViewInShelf;
+ viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection;
if (mNotGoneIndex != -1) {
viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex);
}
- viewState.hasItemsInStableShelf = lastViewState.inShelf;
+
viewState.hidden = !mAmbientState.isShadeExpanded()
- || mAmbientState.isQsCustomizerShowing();
+ || mAmbientState.isQsCustomizerShowing()
+ || algorithmState.firstViewInShelf == null;
+
+ final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf(
+ algorithmState.firstViewInShelf);
+
+ if (mAmbientState.isExpansionChanging()
+ && algorithmState.firstViewInShelf != null
+ && indexOfFirstViewInShelf > 0) {
+
+ // Show shelf if section before it is showing.
+ final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get(
+ indexOfFirstViewInShelf - 1);
+ if (viewBeforeShelf.getViewState().hidden) {
+ viewState.hidden = true;
+ }
+ }
+
+ final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight();
+ viewState.yTranslation = stackEnd - viewState.height;
} else {
viewState.hidden = true;
viewState.location = ExpandableViewState.LOCATION_GONE;
@@ -199,13 +222,11 @@
if (!mShowNotificationShelf) {
return;
}
-
mShelfIcons.resetViewStates();
float shelfStart = getTranslationY();
float numViewsInShelf = 0.0f;
View lastChild = mAmbientState.getLastVisibleBackgroundChild();
mNotGoneIndex = -1;
- float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
// find the first view that doesn't overlap with the shelf
int notGoneIndex = 0;
int colorOfViewBeforeLast = NO_COLOR;
@@ -219,7 +240,7 @@
float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity();
boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold
|| (mAmbientState.isExpansionChanging()
- && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
+ && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold);
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
@@ -233,22 +254,37 @@
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
continue;
}
-
float notificationClipEnd;
boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight
|| child.isPinned();
boolean isLastChild = child == lastChild;
float rowTranslationY = child.getTranslationY();
+
+ final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast,
+ expandingAnimated, isLastChild);
+
+ final float stackEnd = mAmbientState.getStackY()
+ + mAmbientState.getStackHeight();
+ // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount
if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) {
- notificationClipEnd = shelfStart + getIntrinsicHeight();
+ notificationClipEnd = stackEnd;
+ } else if (mAmbientState.isExpansionChanging()) {
+ if (mIndexOfFirstViewInOverflowingSection != -1
+ && i >= mIndexOfFirstViewInOverflowingSection) {
+ // Clip notifications in (section overflowing into shelf) to shelf start.
+ notificationClipEnd = shelfStart - mPaddingBetweenElements;
+ } else {
+ // Clip notifications before the section overflowing into shelf
+ // to stackEnd because we do not show the shelf if the section right before the
+ // shelf is still hidden.
+ notificationClipEnd = stackEnd;
+ }
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
}
int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex);
clipTopAmount = Math.max(clipTop, clipTopAmount);
- final float inShelfAmount = updateShelfTransformation(child, scrollingFast,
- expandingAnimated, isLastChild);
// If the current row is an ExpandableNotificationRow, update its color, roundedness,
// and icon state.
if (child instanceof ExpandableNotificationRow) {
@@ -314,19 +350,23 @@
distanceToGapTop / mGapHeight);
previousAnv.setBottomRoundness(firstElementRoundness,
false /* don't animate */);
- backgroundTop = (int) distanceToGapBottom;
}
}
previousAnv = anv;
}
}
+
clipTransientViews();
setClipTopAmount(clipTopAmount);
- boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight();
- if (mShowNotificationShelf) {
- setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- }
+
+ boolean isHidden = getViewState().hidden
+ || clipTopAmount >= getIntrinsicHeight()
+ || !mShowNotificationShelf
+ || numViewsInShelf < 1f;
+
+ // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
+ setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
setBackgroundTop(backgroundTop);
setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
@@ -339,11 +379,10 @@
continue;
}
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- updateIconClipAmount(row);
updateContinuousClipping(row);
}
- boolean hideBackground = numViewsInShelf < 1.0f;
- setHideBackground(hideBackground || backgroundForceHidden);
+ boolean hideBackground = isHidden;
+ setHideBackground(hideBackground);
if (mNotGoneIndex == -1) {
mNotGoneIndex = notGoneIndex;
}
@@ -476,10 +515,10 @@
/**
* @return the amount how much this notification is in the shelf
*/
- private float updateShelfTransformation(ExpandableView view, boolean scrollingFast,
+ private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast,
boolean expandingAnimated, boolean isLastChild) {
- // Let calculate how much the view is in the shelf
+ // Let's calculate how much the view is in the shelf
float viewStart = view.getTranslationY();
int fullHeight = view.getActualHeight() + mPaddingBetweenElements;
float iconTransformStart = calculateIconTransformationStart(view);
@@ -496,15 +535,21 @@
transformDistance,
view.getMinHeight() - getIntrinsicHeight());
}
+
float viewEnd = viewStart + fullHeight;
float fullTransitionAmount = 0.0f;
float iconTransitionAmount = 0.0f;
float shelfStart = getTranslationY();
-
- if (viewEnd >= shelfStart
+ if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) {
+ // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf
+ if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) {
+ fullTransitionAmount = 1f;
+ iconTransitionAmount = 1f;
+ }
+ } else if (viewEnd >= shelfStart
&& (!mAmbientState.isUnlockHintRunning() || view.isInShelf())
&& (mAmbientState.isShadeExpanded()
- || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
+ || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) {
if (viewStart < shelfStart) {
float fullAmount = (shelfStart - viewStart) / fullHeight;
@@ -572,7 +617,7 @@
&& !mNoAnimationsInThisFrame;
}
iconState.clampedAppearAmount = clampedAmount;
- setIconTransformationAmount(view, transitionAmount, isLastChild);
+ setIconTransformationAmount(view, transitionAmount);
}
private boolean isTargetClipped(ExpandableView view) {
@@ -585,12 +630,10 @@
+ view.getContentTranslation()
+ view.getRelativeTopPadding(target)
+ target.getHeight();
-
return endOfTarget >= getTranslationY() - mPaddingBetweenElements;
}
- private void setIconTransformationAmount(ExpandableView view, float transitionAmount,
- boolean isLastChild) {
+ private void setIconTransformationAmount(ExpandableView view, float transitionAmount) {
if (!(view instanceof ExpandableNotificationRow)) {
return;
}
@@ -601,7 +644,6 @@
return;
}
iconState.alpha = transitionAmount;
-
boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf();
iconState.hidden = isAppearing
|| (view instanceof ExpandableNotificationRow
@@ -610,8 +652,8 @@
|| (transitionAmount == 0.0f && !iconState.isAnimating(icon))
|| row.isAboveShelf()
|| row.showingPulsing()
- || (!row.isInShelf() && isLastChild)
|| row.getTranslationZ() > mAmbientState.getBaseZHeight();
+
iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
// Fade in icons at shelf start
@@ -790,8 +832,19 @@
mController = notificationShelfController;
}
+ public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
+ mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+ }
+
+ public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) {
+ mIndexOfFirstViewInOverflowingSection =
+ mHostLayoutController.indexOfChild(firstViewInOverflowingSection);
+ }
+
private class ShelfState extends ExpandableViewState {
private boolean hasItemsInStableShelf;
+ private ExpandableView firstViewInShelf;
+ private ExpandableView firstViewInOverflowSection;
@Override
public void applyToView(View view) {
@@ -800,6 +853,8 @@
}
super.applyToView(view);
+ setIndexOfFirstViewInShelf(firstViewInShelf);
+ setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
@@ -812,6 +867,8 @@
}
super.animateTo(child, properties);
+ setIndexOfFirstViewInShelf(firstViewInShelf);
+ setFirstViewInOverflowingSection(firstViewInOverflowSection);
updateAppearance();
setHasItemsInStableShelf(hasItemsInStableShelf);
mShelfIcons.setAnimationsEnabled(mAnimationsEnabled);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 1e935c1..4f70fdb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -22,6 +22,7 @@
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter;
@@ -103,9 +104,10 @@
return mView.getHeight();
}
- public void updateState(AmbientState ambientState) {
+ public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
mAmbientState = ambientState;
- mView.updateState(ambientState);
+ mView.updateState(algorithmState, ambientState);
}
public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index c811fdd..a537299 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -25,12 +25,14 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.DimenRes;
+import androidx.annotation.Nullable;
import androidx.core.graphics.ColorUtils;
import com.android.internal.annotations.GuardedBy;
@@ -66,6 +68,8 @@
private Executor mChangeRunnableExecutor;
private Executor mExecutor;
private Looper mExecutorLooper;
+ @Nullable
+ private Rect mDrawableBounds;
public ScrimView(Context context) {
this(context, null);
@@ -125,7 +129,9 @@
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (changed) {
+ if (mDrawableBounds != null) {
+ mDrawable.setBounds(mDrawableBounds);
+ } else if (changed) {
mDrawable.setBounds(left, top, right, bottom);
invalidate();
}
@@ -288,4 +294,15 @@
((ScrimDrawable) mDrawable).setRoundedCorners(radius);
}
}
+
+ /**
+ * Set bounds for the view, all coordinates are absolute
+ */
+ public void setDrawableBounds(float left, float top, float right, float bottom) {
+ if (mDrawableBounds == null) {
+ mDrawableBounds = new Rect();
+ }
+ mDrawableBounds.set((int) left, (int) top, (int) right, (int) bottom);
+ mDrawable.setBounds(mDrawableBounds);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 8446b4e6..caf4720 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -60,7 +60,7 @@
private NotificationShelf mShelf;
private int mZDistanceBetweenElements;
private int mBaseZHeight;
- private int mMaxLayoutHeight;
+ private int mContentHeight;
private ExpandableView mLastVisibleBackgroundChild;
private float mCurrentScrollVelocity;
private int mStatusBarState;
@@ -84,6 +84,75 @@
private boolean mIsShadeOpening;
private float mSectionPadding;
+ /** Distance of top of notifications panel from top of screen. */
+ private float mStackY = 0;
+
+ /** Height of notifications panel. */
+ private float mStackHeight = 0;
+
+ /** Fraction of shade expansion. */
+ private float mExpansionFraction;
+
+ /** Height of the notifications panel without top padding when expansion completes. */
+ private float mStackEndHeight;
+
+ /**
+ * @return Height of the notifications panel without top padding when expansion completes.
+ */
+ public float getStackEndHeight() {
+ return mStackEndHeight;
+ }
+
+ /**
+ * @param stackEndHeight Height of the notifications panel without top padding
+ * when expansion completes.
+ */
+ public void setStackEndHeight(float stackEndHeight) {
+ mStackEndHeight = stackEndHeight;
+ }
+
+ /**
+ * @param stackY Distance of top of notifications panel from top of screen.
+ */
+ public void setStackY(float stackY) {
+ mStackY = stackY;
+ }
+
+ /**
+ * @return Distance of top of notifications panel from top of screen.
+ */
+ public float getStackY() {
+ return mStackY;
+ }
+
+ /**
+ * @param expansionFraction Fraction of shade expansion.
+ */
+ public void setExpansionFraction(float expansionFraction) {
+ mExpansionFraction = expansionFraction;
+ }
+
+ /**
+ * @return Fraction of shade expansion.
+ */
+ public float getExpansionFraction() {
+ return mExpansionFraction;
+ }
+
+ /**
+ * @param stackHeight Height of notifications panel.
+ */
+ public void setStackHeight(float stackHeight) {
+ mStackHeight = stackHeight;
+ }
+
+ /**
+ * @return Height of notifications panel.
+ */
+ public float getStackHeight() {
+ return mStackHeight;
+ }
+
/** Tracks the state from AlertingNotificationManager#hasNotifications() */
private boolean mHasAlertEntries;
@@ -263,8 +332,8 @@
if (mDozeAmount == 1.0f && !isPulseExpanding()) {
return mShelf.getHeight();
}
- int height = Math.max(mLayoutMinHeight,
- Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding);
+ int height = (int) Math.max(mLayoutMinHeight,
+ Math.min(mLayoutHeight, mContentHeight) - mTopPadding);
if (ignorePulseHeight) {
return height;
}
@@ -313,8 +382,12 @@
return mShelf;
}
- public void setLayoutMaxHeight(int maxLayoutHeight) {
- mMaxLayoutHeight = maxLayoutHeight;
+ public void setContentHeight(int contentHeight) {
+ mContentHeight = contentHeight;
+ }
+
+ public float getContentHeight() {
+ return mContentHeight;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index fb72ac3..733a9f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -658,6 +658,14 @@
y = getHeight() - getEmptyBottomMargin();
mDebugPaint.setColor(Color.GREEN);
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+ y = (int) (mAmbientState.getStackY());
+ mDebugPaint.setColor(Color.CYAN);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
+
+ y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight());
+ mDebugPaint.setColor(Color.BLUE);
+ canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
}
}
@@ -1123,18 +1131,37 @@
mTopPaddingNeedsAnimation = true;
mNeedsAnimation = true;
}
+ updateStackPosition();
requestChildrenUpdate();
notifyHeightChangeListener(null, animate);
}
}
/**
+ * Apply expansion fraction to the y position and height of the notifications panel.
+ */
+ private void updateStackPosition() {
+ // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD
+ mAmbientState.setStackY(
+ MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction()));
+ final float shadeBottom = getHeight() - getEmptyBottomMargin();
+ mAmbientState.setStackEndHeight(shadeBottom - mTopPadding);
+ mAmbientState.setStackHeight(
+ MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction()));
+ }
+
+ /**
* Update the height of the panel.
*
* @param height the expanded height of the panel
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void setExpandedHeight(float height) {
+ final float shadeBottom = getHeight() - getEmptyBottomMargin();
+ final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f);
+ mAmbientState.setExpansionFraction(expansionFraction);
+ updateStackPosition();
+
mExpandedHeight = height;
setIsExpanded(height > 0);
int minExpansionHeight = getMinExpansionHeight();
@@ -2067,7 +2094,8 @@
mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin;
updateScrollability();
clampScrollPosition();
- mAmbientState.setLayoutMaxHeight(mContentHeight);
+ updateStackPosition();
+ mAmbientState.setContentHeight(mContentHeight);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7776e69..4fc49ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -788,6 +788,10 @@
return mView.getTranslationX();
}
+ public int indexOfChild(View view) {
+ return mView.indexOfChild(view);
+ }
+
public void setOnHeightChangedListener(
ExpandableView.OnHeightChangedListener listener) {
mView.setOnHeightChangedListener(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index bbdbe80..3e1a781 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -20,21 +20,22 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
-import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* The Algorithm of the {@link com.android.systemui.statusbar.notification.stack
@@ -92,20 +93,16 @@
// First we reset the view states to their default values.
resetChildViewStates();
-
initAlgorithmState(mHostView, algorithmState, ambientState);
-
updatePositionsForState(algorithmState, ambientState);
-
updateZValuesForState(algorithmState, ambientState);
-
updateHeadsUpStates(algorithmState, ambientState);
updatePulsingStates(algorithmState, ambientState);
updateDimmedActivatedHideSensitive(ambientState, algorithmState);
updateClipping(algorithmState, ambientState);
updateSpeedBumpState(algorithmState, speedBumpIndex);
- updateShelfState(ambientState);
+ updateShelfState(algorithmState, ambientState);
getNotificationChildrenStates(algorithmState, ambientState);
}
@@ -144,10 +141,13 @@
}
- private void updateShelfState(AmbientState ambientState) {
+ private void updateShelfState(
+ StackScrollAlgorithmState algorithmState,
+ AmbientState ambientState) {
+
NotificationShelf shelf = ambientState.getShelf();
if (shelf != null) {
- shelf.updateState(ambientState);
+ shelf.updateState(algorithmState, ambientState);
}
}
@@ -172,7 +172,8 @@
&& ((ExpandableNotificationRow) child).isPinned();
if (mClipNotificationScrollToTop
&& (!state.inShelf || (isHeadsUp && !firstHeadsUp))
- && newYTranslation < clipStart) {
+ && newYTranslation < clipStart
+ && !ambientState.isShadeOpening()) {
// The previous view is overlapping on top, clip!
float overlapAmount = clipStart - newYTranslation;
state.clipTopAmount = (int) overlapAmount;
@@ -217,7 +218,6 @@
private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state,
AmbientState ambientState) {
float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
-
int scrollY = ambientState.getScrollY();
// Due to the overScroller, the stackscroller can have negative scroll state. This is
@@ -230,7 +230,6 @@
state.visibleChildren.clear();
state.visibleChildren.ensureCapacity(childCount);
int notGoneIndex = 0;
- ExpandableView lastView = null;
for (int i = 0; i < childCount; i++) {
ExpandableView v = (ExpandableView) hostView.getChildAt(i);
if (v.getVisibility() != View.GONE) {
@@ -255,12 +254,101 @@
}
}
}
- ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
- state.indexOfExpandingNotification = expandingNotification != null
- ? expandingNotification.isChildInGroup()
- ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent())
- : state.visibleChildren.indexOf(expandingNotification)
- : -1;
+
+ state.firstViewInShelf = null;
+ // Save y, sectionStart, sectionEnd from when shade is fully expanded.
+ // Consider updating these states in updateContentView instead so that we don't have to
+ // recalculate in every frame.
+ float currentY = -scrollY;
+ int sectionStartIndex = 0;
+ int sectionEndIndex = 0;
+ for (int i = 0; i < state.visibleChildren.size(); i++) {
+ final ExpandableView view = state.visibleChildren.get(i);
+ // Add space between sections.
+ final boolean applyGapHeight = childNeedsGapHeight(
+ ambientState.getSectionProvider(), i,
+ view, getPreviousView(i, state));
+ if (applyGapHeight) {
+ currentY += mGapHeight;
+ }
+
+ // Save index of first view in the shelf
+ final float shelfStart = ambientState.getStackEndHeight()
+ - ambientState.getShelf().getIntrinsicHeight();
+ if (currentY >= shelfStart
+ && !(view instanceof FooterView)
+ && state.firstViewInShelf == null) {
+ state.firstViewInShelf = view;
+ }
+
+ // Record y position when fully expanded
+ ExpansionData expansionData = new ExpansionData();
+ expansionData.fullyExpandedY = currentY;
+ state.expansionData.put(view, expansionData);
+
+ if (ambientState.getSectionProvider()
+ .beginsSection(view, getPreviousView(i, state))) {
+
+ // Save section start/end for views in the section before this new section
+ ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex);
+ final float sectionStart =
+ state.expansionData.get(sectionStartView).fullyExpandedY;
+
+ ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex);
+ float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY
+ + sectionEndView.getIntrinsicHeight();
+
+ // If we show the shelf, trim section end to shelf start
+ // This means section end > start for views in the shelf
+ if (state.firstViewInShelf != null && sectionEnd > shelfStart) {
+ sectionEnd = shelfStart;
+ }
+
+ // Update section bounds of every view in the previous section
+ // Consider using shared SectionInfo for views in same section to avoid looping back
+ for (int j = sectionStartIndex; j < i; j++) {
+ ExpandableView sectionView = state.visibleChildren.get(j);
+ ExpansionData viewExpansionData =
+ state.expansionData.get(sectionView);
+ viewExpansionData.sectionStart = sectionStart;
+ viewExpansionData.sectionEnd = sectionEnd;
+ state.expansionData.put(sectionView, viewExpansionData);
+ }
+ sectionStartIndex = i;
+
+ if (view instanceof FooterView) {
+ // Also record section bounds for FooterView (same as its own)
+ // because it is the last view and we won't get to this point again
+ // after the loop ends
+ ExpansionData footerExpansionData = state.expansionData.get(view);
+ footerExpansionData.sectionStart = expansionData.fullyExpandedY;
+ footerExpansionData.sectionEnd = expansionData.fullyExpandedY
+ + view.getIntrinsicHeight();
+ state.expansionData.put(view, footerExpansionData);
+ }
+ }
+ sectionEndIndex = i;
+ currentY = currentY
+ + getMaxAllowedChildHeight(view)
+ + mPaddingBetweenElements;
+ }
+
+ // Which view starts the section of the view right before the shelf?
+ // Save it for later when we clip views in that section to shelf start.
+ state.firstViewInOverflowSection = null;
+ if (state.firstViewInShelf != null) {
+ ExpandableView nextView = null;
+ final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf);
+ for (int i = startIndex - 1; i >= 0; i--) {
+ ExpandableView view = state.visibleChildren.get(i);
+ if (nextView != null && ambientState.getSectionProvider()
+ .beginsSection(nextView, view)) {
+ break;
+ }
+ nextView = view;
+ }
+ state.firstViewInOverflowSection = nextView;
+ }
}
private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
@@ -272,6 +360,10 @@
return notGoneIndex;
}
+ private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) {
+ return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+ }
+
/**
* Determine the positions for the views. This is the main part of the algorithm.
*
@@ -288,6 +380,15 @@
}
}
+ private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
+ int i) {
+ expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+ if (currentYPosition <= 0) {
+ expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
+ }
+ }
+
+ // TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
*
@@ -306,53 +407,84 @@
StackScrollAlgorithmState algorithmState,
AmbientState ambientState,
float currentYPosition) {
- ExpandableView child = algorithmState.visibleChildren.get(i);
- ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null;
+
+ ExpandableView view = algorithmState.visibleChildren.get(i);
+ ExpandableViewState viewState = view.getViewState();
+ viewState.location = ExpandableViewState.LOCATION_UNKNOWN;
+ viewState.alpha = 1f - ambientState.getHideAmount();
+
+ if (view.mustStayOnScreen() && viewState.yTranslation >= 0) {
+ // Even if we're not scrolled away we're in view and we're also not in the
+ // shelf. We can relax the constraints and let us scroll off the top!
+ float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
+ viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
+ }
+
+ // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState
+ // Get fraction of section showing, and later apply it to view height and gaps between views
+ float sectionFraction = 1f;
+ boolean showSection = true;
+
+ if (!ambientState.isOnKeyguard()
+ && !ambientState.isPulseExpanding()
+ && ambientState.isExpansionChanging()) {
+
+ final ExpansionData expansionData = algorithmState.expansionData.get(view);
+ final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart;
+ sectionFraction = MathUtils.constrain(
+ (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight,
+ 0f, 1f);
+ showSection = expansionData.sectionStart < ambientState.getStackHeight();
+ }
+
+ // Add gap between sections.
final boolean applyGapHeight =
childNeedsGapHeight(
ambientState.getSectionProvider(), i,
- child, previousChild);
- ExpandableViewState childViewState = child.getViewState();
- childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
-
+ view, getPreviousView(i, algorithmState));
if (applyGapHeight) {
- currentYPosition += mGapHeight;
- }
- int childHeight = getMaxAllowedChildHeight(child);
- childViewState.yTranslation = currentYPosition;
- childViewState.alpha = 1f - ambientState.getHideAmount();
-
- boolean isFooterView = child instanceof FooterView;
- boolean isEmptyShadeView = child instanceof EmptyShadeView;
-
- childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
- float inset = ambientState.getTopPadding() + ambientState.getStackTranslation()
- + ambientState.getSectionPadding();
- if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
- // Even if we're not scrolled away we're in view and we're also not in the
- // shelf. We can relax the constraints and let us scroll off the top!
- float end = childViewState.yTranslation + childViewState.height + inset;
- childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
- }
- if (isFooterView) {
- childViewState.yTranslation = Math.min(childViewState.yTranslation,
- ambientState.getInnerHeight() - childHeight);
- } else if (isEmptyShadeView) {
- childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
- + ambientState.getStackTranslation() * 0.25f;
- } else if (child != ambientState.getTrackedHeadsUpRow()) {
- clampPositionToShelf(child, childViewState, ambientState);
+ currentYPosition += sectionFraction * mGapHeight;
}
- currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
- if (currentYPosition <= 0) {
- childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
- }
- if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
- Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
+ viewState.yTranslation = currentYPosition;
+
+ if (view instanceof SectionHeaderView) {
+ // Add padding before sections for overscroll effect.
+ viewState.yTranslation += ambientState.getSectionPadding();
}
- childViewState.yTranslation += inset;
+ if (view != ambientState.getTrackedHeadsUpRow()) {
+ if (ambientState.isExpansionChanging()) {
+ viewState.hidden = !showSection;
+ viewState.inShelf = algorithmState.firstViewInShelf != null
+ && i >= algorithmState.visibleChildren.indexOf(
+ algorithmState.firstViewInShelf)
+ && !(view instanceof FooterView);
+ } else {
+ // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all
+ // to shelf start, thereby hiding all notifications (except the first one, which we
+ // later unhide in updatePulsingState)
+ final int shelfStart = ambientState.getInnerHeight()
+ - ambientState.getShelf().getIntrinsicHeight();
+ if (!(view instanceof FooterView)) {
+ viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart);
+ }
+ if (viewState.yTranslation >= shelfStart) {
+ viewState.hidden = !view.isExpandAnimationRunning()
+ && !view.hasExpandingChild()
+ && !(view instanceof FooterView);
+ viewState.inShelf = true;
+ // Notifications in the shelf cannot be visible HUNs.
+ viewState.headsUpIsVisible = false;
+ }
+ }
+ viewState.height = (int) MathUtils.lerp(
+ 0, getMaxAllowedChildHeight(view), sectionFraction);
+ }
+
+ currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements;
+ setLocation(view.getViewState(), currentYPosition, i);
+ viewState.yTranslation += ambientState.getStackY();
return currentYPosition;
}
@@ -393,10 +525,10 @@
int visibleIndex,
View child,
View previousChild) {
-
- boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild)
- && visibleIndex > 0;
- return needsGapHeight;
+ return sectionProvider.beginsSection(child, previousChild)
+ && visibleIndex > 0
+ && !(previousChild instanceof SilentHeader)
+ && !(child instanceof FooterView);
}
private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
@@ -514,42 +646,6 @@
childState.yTranslation = newTranslation;
}
- /**
- * Clamp the height of the child down such that its end is at most on the beginning of
- * the shelf.
- *
- * @param childViewState the view state of the child
- * @param ambientState the ambient state
- */
- private void clampPositionToShelf(ExpandableView child,
- ExpandableViewState childViewState,
- AmbientState ambientState) {
- if (ambientState.getShelf() == null) {
- return;
- }
-
- ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow();
- boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null
- && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow);
-
- int shelfStart = ambientState.getInnerHeight()
- - ambientState.getShelf().getIntrinsicHeight();
- if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) {
- // Don't show none heads-up notifications while in appearing phase.
- childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart);
- }
- childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
- if (child instanceof SectionHeaderView) {
- // Add padding before sections for overscroll effect.
- childViewState.yTranslation += ambientState.getSectionPadding();
- }
- if (childViewState.yTranslation >= shelfStart) {
- childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild();
- childViewState.inShelf = true;
- childViewState.headsUpIsVisible = false;
- }
- }
-
protected int getMaxAllowedChildHeight(View child) {
if (child instanceof ExpandableView) {
ExpandableView expandableView = (ExpandableView) child;
@@ -641,6 +737,35 @@
this.mIsExpanded = isExpanded;
}
+ /**
+ * Data used to layout views while shade expansion changes.
+ */
+ public class ExpansionData {
+
+ /**
+ * Y position of top of first view in section.
+ */
+ public float sectionStart;
+
+ /**
+ * Y position of bottom of last view in section.
+ */
+ public float sectionEnd;
+
+ /**
+ * Y position of view when shade is fully expanded.
+ * Does not include distance between top notifications panel and top of screen.
+ */
+ public float fullyExpandedY;
+
+ /**
+ * Whether this notification is in the same section as the notification right before the
+ * shelf. Used to determine which notification should be clipped to shelf start while
+ * shade expansion changes.
+ */
+ public boolean inOverflowingSection;
+ }
+
public class StackScrollAlgorithmState {
/**
@@ -649,15 +774,26 @@
public int scrollY;
/**
+ * First view in shelf.
+ */
+ public ExpandableView firstViewInShelf;
+
+ /**
+ * First view in section overflowing into shelf while shade expansion changes.
+ */
+ public ExpandableView firstViewInOverflowSection;
+
+ /**
+ * Map of view to ExpansionData used for layout during shade expansion.
+ * Use view instead of index as key, because visibleChildren indices do not match the ones
+ * used in the shelf.
+ */
+ public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>();
+
+ /**
* The children from the host view which are not gone.
*/
- public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
-
- private int indexOfExpandingNotification;
-
- public int getIndexOfExpandingNotification() {
- return indexOfExpandingNotification;
- }
+ public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index 707135c3..30d9841 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.phone
+import android.annotation.IntDef
import android.content.Context
import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricSourceType
import android.provider.Settings
import com.android.systemui.Dumpable
+import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -37,9 +39,18 @@
private val mKeyguardStateController: KeyguardStateController
private val statusBarStateController: StatusBarStateController
+ @BypassOverride private val bypassOverride: Int
private var hasFaceFeature: Boolean
private var pendingUnlock: PendingUnlock? = null
+ @IntDef(
+ FACE_UNLOCK_BYPASS_NO_OVERRIDE,
+ FACE_UNLOCK_BYPASS_ALWAYS,
+ FACE_UNLOCK_BYPASS_NEVER
+ )
+ @Retention(AnnotationRetention.SOURCE)
+ private annotation class BypassOverride
+
/**
* Pending unlock info:
*
@@ -60,7 +71,14 @@
* If face unlock dismisses the lock screen or keeps user on keyguard for the current user.
*/
var bypassEnabled: Boolean = false
- get() = field && mKeyguardStateController.isFaceAuthEnabled
+ get() {
+ val enabled = when (bypassOverride) {
+ FACE_UNLOCK_BYPASS_ALWAYS -> true
+ FACE_UNLOCK_BYPASS_NEVER -> false
+ else -> field
+ }
+ return enabled && mKeyguardStateController.isFaceAuthEnabled
+ }
private set
var bouncerShowing: Boolean = false
@@ -86,6 +104,8 @@
this.mKeyguardStateController = keyguardStateController
this.statusBarStateController = statusBarStateController
+ bypassOverride = context.resources.getInteger(R.integer.config_face_unlock_bypass_override)
+
hasFaceFeature = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FACE)
if (!hasFaceFeature) {
return
@@ -198,5 +218,9 @@
companion object {
const val BYPASS_PANEL_FADE_DURATION = 67
+
+ private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0
+ private const val FACE_UNLOCK_BYPASS_ALWAYS = 1
+ private const val FACE_UNLOCK_BYPASS_NEVER = 2
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 6a35293..c4d8840 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1997,12 +1997,35 @@
float qsExpansionFraction = getQsExpansionFraction();
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
- mScrimController.setQsPosition(qsExpansionFraction,
- calculateQsBottomPosition(qsExpansionFraction));
+ int qsPanelBottomY = calculateQsBottomPosition(qsExpansionFraction);
+ mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
+ setNotificationBounds(qsExpansionFraction, qsPanelBottomY);
mNotificationStackScrollLayoutController.setQsExpansionFraction(qsExpansionFraction);
mDepthController.setQsPanelExpansion(qsExpansionFraction);
}
+ private void setNotificationBounds(float qsExpansionFraction, int qsPanelBottomY) {
+ float top = 0;
+ float bottom = 0;
+ float left = 0;
+ float right = 0;
+ if (qsPanelBottomY > 0) {
+ // notification shade is expanding/expanded
+ if (!mShouldUseSplitNotificationShade) {
+ top = qsPanelBottomY;
+ bottom = getView().getBottom();
+ left = getView().getLeft();
+ right = getView().getRight();
+ } else {
+ top = Math.min(qsPanelBottomY, mSplitShadeNotificationsTopPadding);
+ bottom = mNotificationStackScrollLayoutController.getHeight();
+ left = mNotificationStackScrollLayoutController.getLeft();
+ right = mNotificationStackScrollLayoutController.getRight();
+ }
+ }
+ mScrimController.setNotificationsBounds(left, top, right, bottom);
+ }
+
private int calculateQsBottomPosition(float qsExpansionFraction) {
int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
if (qsExpansionFraction != 0.0) {
@@ -2030,7 +2053,7 @@
private float calculateNotificationsTopPadding() {
if (mShouldUseSplitNotificationShade && !mKeyguardShowing) {
- return mSplitShadeNotificationsTopPadding;
+ return mSplitShadeNotificationsTopPadding + mQsNotificationTopPadding;
}
if (mKeyguardShowing && (mQsExpandImmediate
|| mIsExpanding && mQsExpandedWhenExpandingStarted)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 58488ef..5e9c758 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -487,6 +487,13 @@
}
/**
+ * Set bounds for notifications background, all coordinates are absolute
+ */
+ public void setNotificationsBounds(float left, float top, float right, float bottom) {
+ mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
+ }
+
+ /**
* Current state of the QuickSettings when pulling it from the top.
*
* @param expansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
@@ -496,7 +503,6 @@
if (isNaN(expansionFraction)) {
return;
}
- shiftNotificationsScrim(qsPanelBottomY);
updateNotificationsScrimAlpha(expansionFraction, qsPanelBottomY);
if (mQsExpansion != expansionFraction) {
mQsExpansion = expansionFraction;
@@ -511,14 +517,6 @@
}
}
- private void shiftNotificationsScrim(int qsPanelBottomY) {
- if (qsPanelBottomY > 0) {
- mNotificationsScrim.setTranslationY(qsPanelBottomY);
- } else {
- mNotificationsScrim.setTranslationY(0);
- }
- }
-
private void updateNotificationsScrimAlpha(float qsExpansion, int qsPanelBottomY) {
float newAlpha = 0;
if (qsPanelBottomY > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 55b80dd..db77366 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -633,7 +633,7 @@
} else {
mNotificationGroupManager.onEntryRemoved(entry);
}
- });
+ }, mSysuiMainExecutor);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index e2e28b8..a870915 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
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.when;
@@ -49,6 +50,7 @@
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
import org.junit.Test;
@@ -102,8 +104,11 @@
private FrameLayout mLargeClockFrame;
@Mock
BatteryController mBatteryController;
+ @Mock
+ ConfigurationController mConfigurationController;
private KeyguardClockSwitchController mController;
+ private View mStatusArea;
@Before
public void setup() {
@@ -111,6 +116,8 @@
when(mView.findViewById(R.id.left_aligned_notification_icon_container))
.thenReturn(mNotificationIcons);
+ when(mNotificationIcons.getLayoutParams()).thenReturn(
+ mock(RelativeLayout.LayoutParams.class));
when(mView.getContext()).thenReturn(getContext());
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
@@ -135,10 +142,15 @@
mPluginManager,
mFeatureFlags,
mExecutor,
- mBatteryController);
+ mBatteryController,
+ mConfigurationController);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
+
+ mStatusArea = mock(View.class);
+ when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
+
}
@Test
@@ -201,39 +213,40 @@
public void testSmartspacePluginConnectedRemovesKeyguardStatusArea() {
mController.init();
- View statusArea = mock(View.class);
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
-
- View nic = mock(View.class);
- when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
- when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
-
BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
TestView view = mock(TestView.class);
when(plugin.getView(any())).thenReturn(view);
mController.mPluginListener.onPluginConnected(plugin, mContext);
- verify(statusArea).setVisibility(View.GONE);
+ verify(mStatusArea).setVisibility(View.GONE);
}
@Test
public void testSmartspacePluginDisconnectedShowsKeyguardStatusArea() {
mController.init();
- View statusArea = mock(View.class);
- when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(statusArea);
-
- View nic = mock(View.class);
- when(mView.findViewById(R.id.left_aligned_notification_icon_container)).thenReturn(nic);
- when(nic.getLayoutParams()).thenReturn(mock(RelativeLayout.LayoutParams.class));
-
BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
TestView view = mock(TestView.class);
when(plugin.getView(any())).thenReturn(view);
mController.mPluginListener.onPluginConnected(plugin, mContext);
mController.mPluginListener.onPluginDisconnected(plugin);
- verify(statusArea).setVisibility(View.VISIBLE);
+ verify(mStatusArea).setVisibility(View.VISIBLE);
+ }
+
+ @Test
+ public void testThemeChangeNotifiesSmartspace() {
+ mController.init();
+
+ BcSmartspaceDataPlugin plugin = mock(BcSmartspaceDataPlugin.class);
+ TestView view = mock(TestView.class);
+ when(plugin.getView(any())).thenReturn(view);
+
+ mController.mPluginListener.onPluginConnected(plugin, mContext);
+
+ reset(view);
+ mController.getConfigurationListener().onThemeChanged();
+ verify(view).setPrimaryTextColor(anyInt());
}
private void verifyAttachment(VerificationMode times) {
@@ -250,5 +263,7 @@
}
public void registerDataProvider(BcSmartspaceDataPlugin plugin) { }
+
+ public void setPrimaryTextColor(int color) { }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d544f73..42cc1fa9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -49,7 +49,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.face.FaceManager;
@@ -188,8 +187,7 @@
when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
doAnswer(invocation -> {
IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
- callback.onChanged(BiometricSourceType.FACE, true /* enabled */,
- KeyguardUpdateMonitor.getCurrentUser());
+ callback.onChanged(true /* enabled */, KeyguardUpdateMonitor.getCurrentUser());
return null;
}).when(mBiometricManager).registerEnabledOnKeyguardCallback(any());
when(mFaceManager.isHardwareDetected()).thenReturn(true);
@@ -495,6 +493,11 @@
}
private void testFingerprintWhenStrongAuth(int strongAuth) {
+ // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+ // will trigger updateBiometricListeningState();
+ clearInvocations(mFingerprintManager);
+ mKeyguardUpdateMonitor.resetBiometricListeningState();
+
when(mStrongAuthTracker.getStrongAuthForUser(anyInt())).thenReturn(strongAuth);
mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
mTestableLooper.processAllMessages();
@@ -537,7 +540,7 @@
authCallback.onAuthenticationFailed();
// THEN aod interrupt is cancelled
- verify(mAuthController).onCancelAodInterrupt();
+ verify(mAuthController).onCancelUdfps();
}
@Test
@@ -557,7 +560,7 @@
authCallback.onAuthenticationError(0, "");
// THEN aod interrupt is cancelled
- verify(mAuthController).onCancelAodInterrupt();
+ verify(mAuthController).onCancelUdfps();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 3f0831c..78c6717 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -233,9 +233,9 @@
TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME,
AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED);
assertEquals(2,
- mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size());
- assertEquals(1,
- mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size());
+ mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size());
+ assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER),
+ false).size());
}
@Test
@@ -245,11 +245,11 @@
mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true);
assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+ UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true);
assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
+ UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size());
}
@Test
@@ -441,7 +441,19 @@
}
@Test
- public void testOnlyRecordAudioPaused() {
+ public void testPausedPhoneCallMicrophoneFilteredOut() {
+ mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback);
+ mTestableLooper.processAllMessages();
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+
+ assertTrue(mController.getActiveAppOps().isEmpty());
+ }
+
+ @Test
+ public void testOnlyRecordAudioPhoneCallMicrophonePaused() {
mController.addCallback(new int[]{
AppOpsManager.OP_RECORD_AUDIO,
AppOpsManager.OP_CAMERA
@@ -532,6 +544,40 @@
}
@Test
+ public void testPhoneCallMicrophoneFilteredWhenMicDisabled() {
+ mController.addCallback(
+ new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA},
+ mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode());
+ assertFalse(list.get(0).isDisabled());
+
+ // Add a camera op, and disable the microphone. The camera op should be the only op returned
+ mController.onSensorBlockedChanged(MICROPHONE, true);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode());
+
+
+ // Re enable the microphone, and verify the op returns
+ mController.onSensorBlockedChanged(MICROPHONE, false);
+ mTestableLooper.processAllMessages();
+
+ list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+ int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0;
+ assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode());
+ }
+
+ @Test
public void testCameraFilteredWhenCameraDisabled() {
mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA},
mCallback);
@@ -563,6 +609,39 @@
assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode());
}
+ @Test
+ public void testPhoneCallCameraFilteredWhenCameraDisabled() {
+ mController.addCallback(
+ new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA},
+ mCallback);
+ mTestableLooper.processAllMessages();
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode());
+ assertFalse(list.get(0).isDisabled());
+
+ // Add an audio op, and disable the camera. The audio op should be the only op returned
+ mController.onSensorBlockedChanged(CAMERA, true);
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+ mTestableLooper.processAllMessages();
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode());
+
+ // Re enable the camera, and verify the op returns
+ mController.onSensorBlockedChanged(CAMERA, false);
+ mTestableLooper.processAllMessages();
+
+ list = mController.getActiveAppOps();
+ assertEquals(2, list.size());
+ int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1;
+ assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
+ }
+
private class TestHandler extends AppOpsControllerImpl.H {
TestHandler(Looper looper) {
mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index bbd3ce8..0aa182f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -259,7 +259,7 @@
mFgExecutor.runAllReady();
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// WHEN it is cancelled
- mUdfpsController.onCancelAodInterrupt();
+ mUdfpsController.onCancelUdfps();
// THEN the illumination is hidden
verify(mUdfpsView).stopIllumination();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 791dd12..05a1e4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -28,6 +28,7 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
@@ -43,6 +44,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
@@ -86,6 +88,8 @@
private lateinit var privacyLogger: PrivacyLogger
@Mock
private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var appOpsController: AppOpsController
@Captor
private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialog.OnDialogDismissed>
@Captor
@@ -131,6 +135,7 @@
uiExecutor,
privacyLogger,
keyguardStateController,
+ appOpsController,
dialogProvider
)
}
@@ -143,18 +148,27 @@
}
@Test
+ fun testMicMutedParameter() {
+ `when`(appOpsController.isMicMuted).thenReturn(true)
+ controller.showDialog(context)
+ backgroundExecutor.runAllReady()
+
+ verify(permissionManager).getIndicatorAppOpUsageData(true)
+ }
+
+ @Test
fun testPermissionManagerOnlyCalledInBackgroundThread() {
controller.showDialog(context)
- verify(permissionManager, never()).indicatorAppOpUsageData
+ verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean())
backgroundExecutor.runAllReady()
- verify(permissionManager).indicatorAppOpUsageData
+ verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean())
}
@Test
fun testPackageManagerOnlyCalledInBackgroundThread() {
val usage = createMockPermGroupUsage()
`when`(usage.isPhoneCall).thenReturn(false)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
controller.showDialog(context)
verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
@@ -217,7 +231,7 @@
isPhoneCall = false,
attribution = TEST_ATTRIBUTION
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage))
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
controller.showDialog(context)
exhaustExecutors()
@@ -246,7 +260,7 @@
packageName = "${TEST_PACKAGE_NAME}_microphone",
permGroupName = PERM_MICROPHONE
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_microphone, usage_camera)
)
@@ -269,7 +283,7 @@
packageName = "${TEST_PACKAGE_NAME}_recent",
isActive = false
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_recent, usage_active)
)
@@ -292,7 +306,7 @@
isActive = true,
lastAccess = 1L
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_active, usage_active_moreRecent)
)
controller.showDialog(context)
@@ -319,7 +333,7 @@
isActive = false,
lastAccess = 2L
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_recent, usage_mostRecent, usage_moreRecent)
)
@@ -342,7 +356,7 @@
permGroupName = PERM_LOCATION
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_camera, usage_location, usage_microphone)
)
`when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -366,7 +380,7 @@
permGroupName = PERM_LOCATION
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_camera, usage_location, usage_microphone)
)
`when`(privacyItemController.locationAvailable).thenReturn(false)
@@ -392,7 +406,7 @@
permGroupName = PERM_LOCATION
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_camera, usage_location, usage_microphone)
)
`when`(privacyItemController.micCameraAvailable).thenReturn(true)
@@ -416,7 +430,7 @@
permGroupName = PERM_LOCATION
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
listOf(usage_camera, usage_location, usage_microphone)
)
`when`(privacyItemController.micCameraAvailable).thenReturn(false)
@@ -433,7 +447,8 @@
val usage_enterprise = createMockPermGroupUsage(
uid = generateUidForUser(ENT_USER_ID)
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise))
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+ .thenReturn(listOf(usage_enterprise))
controller.showDialog(context)
exhaustExecutors()
@@ -446,7 +461,8 @@
val usage_other = createMockPermGroupUsage(
uid = generateUidForUser(ENT_USER_ID + 1)
)
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other))
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+ .thenReturn(listOf(usage_other))
controller.showDialog(context)
exhaustExecutors()
@@ -514,7 +530,8 @@
}
private fun setUpDefaultMockResponses() {
- `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList())
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList())
+ `when`(appOpsController.isMicMuted).thenReturn(false)
`when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
.thenAnswer {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index bba1c6a..e4d7b1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -46,7 +46,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyList
import org.mockito.Captor
import org.mockito.Mock
@@ -156,7 +156,7 @@
fun testDistinctItems() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ .`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
@@ -168,7 +168,7 @@
fun testSimilarItemsDifferentTimeStamp() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ .`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
@@ -215,7 +215,7 @@
@Test
fun testMultipleCallbacksAreUpdated() {
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
val otherCallback = mock(PrivacyItemController.Callback::class.java)
privacyItemController.addCallback(callback)
@@ -233,7 +233,7 @@
@Test
fun testRemoveCallback() {
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
val otherCallback = mock(PrivacyItemController.Callback::class.java)
privacyItemController.addCallback(callback)
privacyItemController.addCallback(otherCallback)
@@ -254,7 +254,7 @@
fun testListShouldNotHaveNull() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ .`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
executor.runAllReady()
@@ -292,7 +292,7 @@
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ .`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
@@ -306,7 +306,7 @@
@Test
fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ .`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
changeLocation(false)
@@ -338,7 +338,7 @@
fun testLogListUpdated() {
doReturn(listOf(
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0))
- ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ ).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
@@ -362,10 +362,10 @@
}
@Test
- fun testListRequestedForAllUsers() {
+ fun testListRequestedShowPaused() {
privacyItemController.addCallback(callback)
executor.runAllReady()
- verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL)
+ verify(appOpsController).getActiveAppOps(true)
}
@Test
@@ -377,7 +377,7 @@
doReturn(listOf(
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0))
- ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ ).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
executor.runAllReady()
@@ -401,7 +401,7 @@
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0),
AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0))
- ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ ).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
executor.runAllReady()
@@ -424,7 +424,7 @@
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0),
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0))
- ).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ ).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext)
executor.runAllReady()
@@ -442,7 +442,7 @@
fun testPassageOfTimeDoesNotRemoveIndicators() {
doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
- )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ )).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
@@ -458,12 +458,12 @@
// Start with some element at time 0
doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
- )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ )).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
// Then remove it at time HOLD + 1
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1)
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -481,12 +481,12 @@
// Start with some element at time 0
doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
- )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ )).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
// Then remove it at time HOLD - 1
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -504,12 +504,12 @@
// Start with some element at time 0
doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
- )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ )).`when`(appOpsController).getActiveAppOps(anyBoolean())
privacyItemController.addCallback(callback)
executor.runAllReady()
// Then remove it at time HOLD - 1
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
+ doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
@@ -525,6 +525,30 @@
assertTrue(privacyItemController.privacyList.isEmpty())
}
+ @Test
+ fun testPausedElementsAreRemoved() {
+ val item = AppOpItem(
+ AppOpsManager.OP_RECORD_AUDIO,
+ TEST_UID,
+ TEST_PACKAGE_NAME,
+ elapsedTime
+ )
+ `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item))
+ privacyItemController.addCallback(callback)
+ executor.runAllReady()
+
+ item.isDisabled = true
+ fakeClock.advanceTime(1)
+ verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
+ argCaptorCallback.value.onActiveStateChanged(
+ AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
+
+ executor.runAllReady()
+
+ verify(callback).onPrivacyItemsChanged(emptyList())
+ assertTrue(privacyItemController.privacyList.isEmpty())
+ }
+
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
index acedf59..4f88599 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java
@@ -81,7 +81,7 @@
mTestableLooper.runWithLooper(() -> {
mQsPanel = new QSPanel(mContext, null);
- mQsPanel.initialize(false);
+ mQsPanel.initialize();
mQsPanel.onFinishInflate();
// Provides a parent with non-zero size for QSPanel
mParentView = new FrameLayout(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 62cc9b7..3d53062 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -49,7 +49,7 @@
MockitoAnnotations.initMocks(this);
TestableLooper.get(this).runWithLooper(() -> mTileAdapter =
- new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake(), /* qsFlag */false));
+ new TileAdapter(mContext, mQSTileHost, new UiEventLoggerFake()));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
index 87a7757..c2e58ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -19,6 +19,7 @@
import static junit.framework.Assert.assertEquals;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.testing.AndroidTestingRunner;
@@ -89,4 +90,17 @@
mView.setTint(tint);
assertEquals(mView.getTint(), tint);
}
+
+ @Test
+ public void setDrawableBounds_propagatesToDrawable() {
+ ColorDrawable drawable = new ColorDrawable();
+ Rect expectedBounds = new Rect(100, 100, 100, 100);
+ mView.setDrawable(drawable);
+ mView.setDrawableBounds(100, 100, 100, 100);
+
+ assertEquals(expectedBounds, drawable.getBounds());
+ // set bounds that are different from expected drawable bounds
+ mView.onLayout(true, 200, 200, 200, 200);
+ assertEquals(expectedBounds, drawable.getBounds());
+ }
}
diff --git a/services/core/java/com/android/server/BinderCallsStatsService.java b/services/core/java/com/android/server/BinderCallsStatsService.java
index c49b8e8..78610a2 100644
--- a/services/core/java/com/android/server/BinderCallsStatsService.java
+++ b/services/core/java/com/android/server/BinderCallsStatsService.java
@@ -138,6 +138,8 @@
private static final String SETTINGS_COLLECT_LATENCY_DATA_KEY = "collect_Latency_data";
private static final String SETTINGS_LATENCY_OBSERVER_SAMPLING_INTERVAL_KEY =
"latency_observer_sampling_interval";
+ private static final String SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY =
+ "latency_observer_push_interval_minutes";
private static final String SETTINGS_LATENCY_HISTOGRAM_BUCKET_COUNT_KEY =
"latency_histogram_bucket_count";
private static final String SETTINGS_LATENCY_HISTOGRAM_FIRST_BUCKET_SIZE_KEY =
@@ -218,7 +220,9 @@
mParser.getFloat(
SETTINGS_LATENCY_HISTOGRAM_BUCKET_SCALE_FACTOR_KEY,
BinderLatencyObserver.BUCKET_SCALE_FACTOR_DEFAULT));
-
+ binderLatencyObserver.setPushInterval(mParser.getInt(
+ SETTINGS_LATENCY_OBSERVER_PUSH_INTERVAL_MINUTES_KEY,
+ BinderLatencyObserver.STATSD_PUSH_INTERVAL_MINUTES_DEFAULT));
final boolean enabled =
mParser.getBoolean(SETTINGS_ENABLED_KEY, BinderCallsStats.ENABLED_DEFAULT);
diff --git a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
index aa56da5..197321f 100644
--- a/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
+++ b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
@@ -106,6 +107,7 @@
}
@VisibleForTesting
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
void handleAirplaneModeChange() {
if (shouldSkipAirplaneModeChange()) {
Log.i(TAG, "Ignore airplane mode change");
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 09cfac0..feed220 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -23,6 +23,8 @@
import static android.os.UserHandle.USER_SYSTEM;
import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -304,6 +306,19 @@
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
"Need BLUETOOTH_PRIVILEGED permission");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return onFactoryResetInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ private boolean onFactoryResetInternal() {
// Wait for stable state if bluetooth is temporary state.
int state = getState();
if (state == BluetoothAdapter.STATE_BLE_TURNING_ON
@@ -343,6 +358,7 @@
return false;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void onAirplaneModeChanged() {
synchronized (this) {
if (isBluetoothPersistedStateOn()) {
@@ -707,9 +723,6 @@
}
public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
- if (!checkConnectPermissionForPreflight(mContext)) {
- return;
- }
if (callback == null) {
Slog.w(TAG, "registerStateChangeCallback: Callback is null!");
return;
@@ -720,9 +733,6 @@
}
public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) {
- if (!checkConnectPermissionForPreflight(mContext)) {
- return;
- }
if (callback == null) {
Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!");
return;
@@ -935,6 +945,7 @@
return appCount;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean checkBluetoothPermissions(String packageName, boolean requireForeground) {
if (isBluetoothDisallowed()) {
if (DBG) {
@@ -990,6 +1001,7 @@
return true;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean disableBle(String packageName, IBinder token) throws RemoteException {
if (!checkBluetoothPermissions(packageName, false)) {
if (DBG) {
@@ -1040,6 +1052,7 @@
* Call IBluetooth.onLeServiceUp() to continue if Bluetooth should be on,
* call IBluetooth.onBrEdrDown() to disable if Bluetooth should be off.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
private void continueFromBleOnState() {
if (DBG) {
Slog.d(TAG, "continueFromBleOnState()");
@@ -1072,6 +1085,10 @@
* Inform BluetoothAdapter instances that BREDR part is down
* and turn off all service and stack if no LE app needs it
*/
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
private void sendBrEdrDownCallback() {
if (DBG) {
Slog.d(TAG, "Calling sendBrEdrDownCallback callbacks");
@@ -1265,12 +1282,14 @@
*
* @hide
*/
+ @SuppressLint("AndroidFrameworkRequiresPermission")
private boolean checkBluetoothPermissionWhenWirelessConsentRequired() {
int result = mContext.checkCallingPermission(
android.Manifest.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED);
return result == PackageManager.PERMISSION_GRANTED;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void unbindAndFinish() {
if (DBG) {
Slog.d(TAG, "unbindAndFinish(): " + mBluetooth + " mBinding = " + mBinding
@@ -2300,6 +2319,10 @@
}
}
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED
+ })
private void restartForReason(int reason) {
try {
mBluetoothLock.readLock().lock();
@@ -2376,6 +2399,7 @@
}
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void handleEnable(boolean quietMode) {
mQuietEnable = quietMode;
@@ -2418,6 +2442,7 @@
return true;
}
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void handleDisable() {
try {
mBluetoothLock.readLock().lock();
@@ -2475,6 +2500,10 @@
mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT);
}
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
private void bluetoothStateChangeHandler(int prevState, int newState) {
boolean isStandardBroadcast = true;
if (prevState == newState) { // No change. Nothing to do.
@@ -2615,6 +2644,10 @@
}
}
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
private void recoverBluetoothServiceFromError(boolean clearBle) {
Slog.e(TAG, "recoverBluetoothServiceFromError");
try {
@@ -2848,6 +2881,7 @@
*
* <p>Should be used in situations where the app op should not be noted.
*/
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private static boolean checkConnectPermissionForPreflight(Context context) {
int permissionCheckResult = PermissionChecker.checkCallingOrSelfPermissionForPreflight(
context, BLUETOOTH_CONNECT);
diff --git a/services/core/java/com/android/server/BluetoothModeChangeHelper.java b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
index 242fa84..3642e4d 100644
--- a/services/core/java/com/android/server/BluetoothModeChangeHelper.java
+++ b/services/core/java/com/android/server/BluetoothModeChangeHelper.java
@@ -16,6 +16,7 @@
package com.android.server;
+import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothHearingAid;
@@ -101,6 +102,7 @@
}
@VisibleForTesting
+ @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public void onAirplaneModeChanged(BluetoothManagerService managerService) {
managerService.onAirplaneModeChanged();
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d9cc4b4..4d96162 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1353,8 +1353,8 @@
new NetworkInfo(TYPE_NONE, 0, "", ""),
new LinkProperties(), new NetworkCapabilities(),
new NetworkScore.Builder().setLegacyInt(0).build(), mContext, null,
- new NetworkAgentConfig(), this, null, null, 0, INVALID_UID, mQosCallbackTracker,
- mDeps);
+ new NetworkAgentConfig(), this, null, null, 0, INVALID_UID,
+ mLingerDelayMs, mQosCallbackTracker, mDeps);
}
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
@@ -3167,6 +3167,11 @@
} else {
logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
}
+ break;
+ }
+ case NetworkAgent.EVENT_LINGER_DURATION_CHANGED: {
+ nai.setLingerDuration((int) arg.second);
+ break;
}
}
}
@@ -6516,7 +6521,8 @@
final NetworkAgentInfo nai = new NetworkAgentInfo(na,
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
currentScore, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig),
- this, mNetd, mDnsResolver, providerId, uid, mQosCallbackTracker, mDeps);
+ this, mNetd, mDnsResolver, providerId, uid, mLingerDelayMs,
+ mQosCallbackTracker, mDeps);
// Make sure the LinkProperties and NetworkCapabilities reflect what the agent info says.
processCapabilitiesFromAgent(nai, nc);
@@ -7759,7 +7765,7 @@
log(" accepting network in place of " + previousSatisfier.toShortString());
}
previousSatisfier.removeRequest(previousRequest.requestId);
- previousSatisfier.lingerRequest(previousRequest.requestId, now, mLingerDelayMs);
+ previousSatisfier.lingerRequest(previousRequest.requestId, now);
} else {
if (VDBG || DDBG) log(" accepting network in place of null");
}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 9f91dd6..483250a 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -174,9 +174,9 @@
# Disk usage stats for verifying quota correctness
3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2)
# Snapshot statistics
-3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3)
+3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3)
# Snapshot rebuild instance
-3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3)
+3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3)
# ---------------------------
# InputMethodManagerService.java
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index df6ab5d..3ba4c34 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -19,9 +19,10 @@
import static android.Manifest.permission.MANAGE_SENSOR_PRIVACY;
import static android.app.ActivityManager.RunningServiceInfo;
import static android.app.ActivityManager.RunningTaskInfo;
-import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
@@ -140,11 +141,15 @@
private final UserManagerInternal mUserManagerInternal;
private final ActivityManager mActivityManager;
private final ActivityTaskManager mActivityTaskManager;
+ private final AppOpsManager mAppOpsManager;
+
+ private final IBinder mAppOpsRestrictionToken = new Binder();
private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal;
public SensorPrivacyService(Context context) {
super(context);
+ mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUserManagerInternal = getLocalService(UserManagerInternal.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
mActivityManager = context.getSystemService(ActivityManager.class);
@@ -194,10 +199,20 @@
}
}
+ for (int i = 0; i < mIndividualEnabled.size(); i++) {
+ int userId = mIndividualEnabled.keyAt(i);
+ SparseBooleanArray userIndividualEnabled =
+ mIndividualEnabled.get(i);
+ for (int j = 0; j < userIndividualEnabled.size(); j++) {
+ int sensor = userIndividualEnabled.keyAt(i);
+ boolean enabled = userIndividualEnabled.valueAt(j);
+ setUserRestriction(userId, sensor, enabled);
+ }
+ }
+
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA};
- AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
- appOpsManager.startWatchingNoted(micAndCameraOps, this);
- appOpsManager.startWatchingStarted(micAndCameraOps, this);
+ mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
+ mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
@@ -221,7 +236,7 @@
public void onOpNoted(int code, int uid, String packageName,
String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.Mode int result) {
- if (result != MODE_ALLOWED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
+ if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
return;
}
@@ -1125,6 +1140,9 @@
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
+
+ setUserRestriction(userId, sensor, enabled);
+
if (listenersForUser == null) {
return;
}
@@ -1152,6 +1170,18 @@
}
}
+ private void setUserRestriction(int userId, int sensor, boolean enabled) {
+ if (sensor == CAMERA) {
+ mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled,
+ mAppOpsRestrictionToken, new String[]{}, userId);
+ } else if (sensor == MICROPHONE) {
+ mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled,
+ mAppOpsRestrictionToken, new String[]{}, userId);
+ mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled,
+ mAppOpsRestrictionToken, new String[]{}, userId);
+ }
+ }
+
private final class DeathRecipient implements IBinder.DeathRecipient {
private ISensorPrivacyListener mListener;
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 611fe7a..2d486c4 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -376,7 +376,7 @@
private final LocalLog mListenLog = new LocalLog(200);
- private List<PhysicalChannelConfig> mPhysicalChannelConfigs;
+ private List<List<PhysicalChannelConfig>> mPhysicalChannelConfigs;
private boolean[] mIsDataEnabled;
@@ -716,7 +716,7 @@
mTelephonyDisplayInfos[i] = null;
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
- mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+ mPhysicalChannelConfigs.add(i, new ArrayList<>());
mAllowedNetworkTypeReason[i] = -1;
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, new ArrayList<>());
@@ -816,7 +816,7 @@
mTelephonyDisplayInfos[i] = null;
mIsDataEnabled[i] = false;
mDataEnabledReason[i] = TelephonyManager.DATA_ENABLED_REASON_USER;
- mPhysicalChannelConfigs.add(i, new PhysicalChannelConfig.Builder().build());
+ mPhysicalChannelConfigs.add(i, new ArrayList<>());
mAllowedNetworkTypeReason[i] = -1;
mAllowedNetworkTypeValue[i] = -1;
mLinkCapacityEstimateLists.add(i, new ArrayList<>());
@@ -1314,8 +1314,9 @@
try {
r.callback.onPhysicalChannelConfigChanged(
shouldSanitizeLocationForPhysicalChannelConfig(r)
- ? getLocationSanitizedConfigs(mPhysicalChannelConfigs)
- : mPhysicalChannelConfigs);
+ ? getLocationSanitizedConfigs(
+ mPhysicalChannelConfigs.get(phoneId))
+ : mPhysicalChannelConfigs.get(phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2550,11 +2551,12 @@
* Send a notification to registrants that the configs of physical channel has changed for
* a particular subscription.
*
+ * @param phoneId the phone id.
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
*/
- public void notifyPhysicalChannelConfigForSubscriber(
- int subId, List<PhysicalChannelConfig> configs) {
+ public void notifyPhysicalChannelConfigForSubscriber(int phoneId, int subId,
+ List<PhysicalChannelConfig> configs) {
if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) {
return;
}
@@ -2566,9 +2568,8 @@
}
synchronized (mRecords) {
- int phoneId = SubscriptionManager.getPhoneId(subId);
if (validatePhoneId(phoneId)) {
- mPhysicalChannelConfigs.set(phoneId, configs.get(phoneId));
+ mPhysicalChannelConfigs.set(phoneId, configs);
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_PHYSICAL_CHANNEL_CONFIG_CHANGED)
@@ -2775,6 +2776,7 @@
pw.println("mDataEnabledReason=" + mDataEnabledReason);
pw.println("mAllowedNetworkTypeReason=" + mAllowedNetworkTypeReason[i]);
pw.println("mAllowedNetworkTypeValue=" + mAllowedNetworkTypeValue[i]);
+ pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs.get(i));
pw.println("mLinkCapacityEstimateList=" + mLinkCapacityEstimateLists.get(i));
pw.decreaseIndent();
}
@@ -2785,7 +2787,6 @@
pw.println("mEmergencyNumberList=" + mEmergencyNumberList);
pw.println("mDefaultPhoneId=" + mDefaultPhoneId);
pw.println("mDefaultSubId=" + mDefaultSubId);
- pw.println("mPhysicalChannelConfigs=" + mPhysicalChannelConfigs);
pw.decreaseIndent();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a1a897..e3b06d6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2500,7 +2500,7 @@
@Override
public void batterySendBroadcast(Intent intent) {
synchronized (this) {
- broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
+ broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null, null,
OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.USER_ALL);
}
@@ -3940,7 +3940,7 @@
intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
broadcastIntentLocked(null, null, null, intent,
- null, null, 0, null, null, null, OP_NONE,
+ null, null, 0, null, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.getUserId(uid));
}
@@ -7531,7 +7531,7 @@
| Intent.FLAG_RECEIVER_FOREGROUND);
intent.putExtra(Intent.EXTRA_USER_HANDLE, currentUserId);
broadcastIntentLocked(null, null, null, intent,
- null, null, 0, null, null, null, OP_NONE,
+ null, null, 0, null, null, null, null, OP_NONE,
null, false, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
currentUserId);
intent = new Intent(Intent.ACTION_USER_STARTING);
@@ -7543,8 +7543,8 @@
public void performReceive(Intent intent, int resultCode,
String data, Bundle extras, boolean ordered, boolean sticky,
int sendingUser) {}
- }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, OP_NONE, null,
- true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
+ }, 0, null, null, new String[] {INTERACT_ACROSS_USERS}, null, OP_NONE,
+ null, true, false, MY_PID, SYSTEM_UID, callingUid, callingPid,
UserHandle.USER_ALL);
} catch (Throwable e) {
Slog.wtf(TAG, "Failed sending first user broadcasts", e);
@@ -12302,7 +12302,7 @@
Intent intent = allSticky.get(i);
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, null,
- null, null, -1, -1, false, null, null, OP_NONE, null, receivers,
+ null, null, -1, -1, false, null, null, null, OP_NONE, null, receivers,
null, 0, null, null, false, true, true, -1, false, null,
false /* only PRE_BOOT_COMPLETED should be exempt, no stickies */);
queue.enqueueParallelBroadcastLocked(r);
@@ -12544,13 +12544,13 @@
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
- boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
- int realCallingPid, int userId) {
+ Bundle resultExtras, String[] requiredPermissions, String[] excludedPermissions,
+ int appOp, Bundle bOptions, boolean ordered, boolean sticky, int callingPid,
+ int callingUid, int realCallingUid, int realCallingPid, int userId) {
return broadcastIntentLocked(callerApp, callerPackage, callerFeatureId, intent,
resolvedType, resultTo, resultCode, resultData, resultExtras, requiredPermissions,
- appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
- realCallingPid, userId, false /* allowBackgroundActivityStarts */,
+ excludedPermissions, appOp, bOptions, ordered, sticky, callingPid, callingUid,
+ realCallingUid, realCallingPid, userId, false /* allowBackgroundActivityStarts */,
null /* tokenNeededForBackgroundActivityStarts */, null /* broadcastAllowList */);
}
@@ -12558,9 +12558,11 @@
final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage,
@Nullable String callerFeatureId, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
- Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
- boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
- int realCallingPid, int userId, boolean allowBackgroundActivityStarts,
+ Bundle resultExtras, String[] requiredPermissions,
+ String[] excludedPermissions, int appOp, Bundle bOptions,
+ boolean ordered, boolean sticky, int callingPid, int callingUid,
+ int realCallingUid, int realCallingPid, int userId,
+ boolean allowBackgroundActivityStarts,
@Nullable IBinder backgroundActivityStartsToken,
@Nullable int[] broadcastAllowList) {
intent = new Intent(intent);
@@ -13139,8 +13141,8 @@
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
- resultCode, resultData, resultExtras, ordered, sticky, false, userId,
+ requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers,
+ resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId,
allowBackgroundActivityStarts, backgroundActivityStartsToken,
timeoutExempt);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
@@ -13237,10 +13239,10 @@
BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
- resultData, resultExtras, ordered, sticky, false, userId,
- allowBackgroundActivityStarts, backgroundActivityStartsToken,
- timeoutExempt);
+ requiredPermissions, excludedPermissions, appOp, brOptions,
+ receivers, resultTo, resultCode, resultData, resultExtras,
+ ordered, sticky, false, userId, allowBackgroundActivityStarts,
+ backgroundActivityStartsToken, timeoutExempt);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
@@ -13366,14 +13368,14 @@
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
return broadcastIntentWithFeature(caller, null, intent, resolvedType, resultTo, resultCode,
- resultData, resultExtras, requiredPermissions, appOp, bOptions, serialized, sticky,
- userId);
+ resultData, resultExtras, requiredPermissions, null, appOp, bOptions, serialized,
+ sticky, userId);
}
public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
- String[] requiredPermissions, int appOp, Bundle bOptions,
+ String[] requiredPermissions, String[] excludedPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
@@ -13388,8 +13390,8 @@
return broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
- requiredPermissions, appOp, bOptions, serialized, sticky,
- callingPid, callingUid, callingUid, callingPid, userId);
+ requiredPermissions, excludedPermissions, appOp, bOptions, serialized,
+ sticky, callingPid, callingUid, callingUid, callingPid, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -13410,7 +13412,7 @@
: new String[] {requiredPermission};
try {
return broadcastIntentLocked(null, packageName, featureId, intent, resolvedType,
- resultTo, resultCode, resultData, resultExtras, requiredPermissions,
+ resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
OP_NONE, bOptions, serialized, sticky, -1, uid, realCallingUid,
realCallingPid, userId, allowBackgroundActivityStarts,
backgroundActivityStartsToken,
@@ -15615,7 +15617,7 @@
return ActivityManagerService.this.broadcastIntentLocked(null /*callerApp*/,
null /*callerPackage*/, null /*callingFeatureId*/, intent,
null /*resolvedType*/, resultTo, 0 /*resultCode*/, null /*resultData*/,
- null /*resultExtras*/, requiredPermissions, AppOpsManager.OP_NONE,
+ null /*resultExtras*/, requiredPermissions, null, AppOpsManager.OP_NONE,
bOptions /*options*/, serialized, false /*sticky*/, callingPid,
callingUid, callingUid, callingPid, userId,
false /*allowBackgroundStarts*/,
@@ -15740,8 +15742,8 @@
| Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
- Binder.getCallingPid(), UserHandle.USER_ALL);
+ null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
@@ -15751,8 +15753,9 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- OP_NONE, null, false, false, MY_PID, SYSTEM_UID, Binder.getCallingUid(),
- Binder.getCallingPid(), UserHandle.USER_ALL);
+ null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ UserHandle.USER_ALL);
}
// Send a broadcast to PackageInstallers if the configuration change is interesting
@@ -15766,7 +15769,7 @@
String[] permissions =
new String[] { android.Manifest.permission.INSTALL_PACKAGES };
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null,
- permissions, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+ permissions, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
}
}
@@ -15791,7 +15794,7 @@
}
broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
- OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
+ null, OP_NONE, null, false, false, -1, SYSTEM_UID, Binder.getCallingUid(),
Binder.getCallingPid(), UserHandle.USER_ALL);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 42aac29..6fa8ecd4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -760,7 +760,7 @@
pw.flush();
Bundle bundle = mBroadcastOptions == null ? null : mBroadcastOptions.toBundle();
mInterface.broadcastIntentWithFeature(null, null, intent, null, receiver, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, bundle, true, false,
+ requiredPermissions, null, android.app.AppOpsManager.OP_NONE, bundle, true, false,
mUserId);
if (!mAsync) {
receiver.waitForFinish();
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index a5474d0b..f0b116c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1419,6 +1419,7 @@
skip = true;
}
}
+
boolean isSingleton = false;
try {
isSingleton = mService.isSingleton(info.activityInfo.processName,
@@ -1553,6 +1554,37 @@
+ info.activityInfo.applicationInfo.uid + " : user is not running");
}
+ if (!skip && r.excludedPermissions != null && r.excludedPermissions.length > 0) {
+ for (int i = 0; i < r.excludedPermissions.length; i++) {
+ String excludedPermission = r.excludedPermissions[i];
+ try {
+ perm = AppGlobals.getPackageManager()
+ .checkPermission(excludedPermission,
+ info.activityInfo.applicationInfo.packageName,
+ UserHandle
+ .getUserId(info.activityInfo.applicationInfo.uid));
+ } catch (RemoteException e) {
+ perm = PackageManager.PERMISSION_DENIED;
+ }
+
+ if (perm == PackageManager.PERMISSION_GRANTED) {
+ skip = true;
+ break;
+ }
+
+ int appOp = AppOpsManager.permissionToOpCode(excludedPermission);
+ if (appOp != AppOpsManager.OP_NONE) {
+ if (mService.getAppOpsManager().checkOpNoThrow(appOp,
+ info.activityInfo.applicationInfo.uid,
+ info.activityInfo.packageName)
+ == AppOpsManager.MODE_ALLOWED) {
+ skip = true;
+ break;
+ }
+ }
+ }
+ }
+
if (!skip && info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
r.requiredPermissions != null && r.requiredPermissions.length > 0) {
for (int i = 0; i < r.requiredPermissions.length; i++) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 198ba34..8015596 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -62,6 +62,7 @@
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
final String[] requiredPermissions; // permissions the caller has required
+ final String[] excludedPermissions; // permissions to exclude
final int appOp; // an app op that is associated with this broadcast
final BroadcastOptions options; // BroadcastOptions supplied by caller
final List receivers; // contains BroadcastFilter and ResolveInfo
@@ -142,6 +143,10 @@
pw.print(Arrays.toString(requiredPermissions));
pw.print(" appOp="); pw.println(appOp);
}
+ if (excludedPermissions != null && excludedPermissions.length > 0) {
+ pw.print(prefix); pw.print("excludedPermissions=");
+ pw.print(Arrays.toString(excludedPermissions));
+ }
if (options != null) {
pw.print(prefix); pw.print("options="); pw.println(options.toBundle());
}
@@ -240,11 +245,11 @@
Intent _intent, ProcessRecord _callerApp, String _callerPackage,
@Nullable String _callerFeatureId, int _callingPid, int _callingUid,
boolean _callerInstantApp, String _resolvedType,
- String[] _requiredPermissions, int _appOp, BroadcastOptions _options, List _receivers,
- IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras,
- boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId,
- boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken,
- boolean timeoutExempt) {
+ String[] _requiredPermissions, String[] _excludedPermissions, int _appOp,
+ BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, int _resultCode,
+ String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky,
+ boolean _initialSticky, int _userId, boolean allowBackgroundActivityStarts,
+ @Nullable IBinder backgroundActivityStartsToken, boolean timeoutExempt) {
if (_intent == null) {
throw new NullPointerException("Can't construct with a null intent");
}
@@ -259,6 +264,7 @@
callerInstantApp = _callerInstantApp;
resolvedType = _resolvedType;
requiredPermissions = _requiredPermissions;
+ excludedPermissions = _excludedPermissions;
appOp = _appOp;
options = _options;
receivers = _receivers;
@@ -299,6 +305,7 @@
userId = from.userId;
resolvedType = from.resolvedType;
requiredPermissions = from.requiredPermissions;
+ excludedPermissions = from.excludedPermissions;
appOp = from.appOp;
options = from.options;
receivers = from.receivers;
@@ -356,8 +363,8 @@
// build a new BroadcastRecord around that single-target list
BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
- requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode,
- resultData, resultExtras, ordered, sticky, initialSticky, userId,
+ requiredPermissions, excludedPermissions, appOp, options, splitReceivers, resultTo,
+ resultCode, resultData, resultExtras, ordered, sticky, initialSticky, userId,
allowBackgroundActivityStarts, mBackgroundActivityStartsToken, timeoutExempt);
split.splitToken = this.splitToken;
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 984fe40..7562098 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -124,7 +124,7 @@
REASON_PRE_BOOT_COMPLETED, "");
synchronized (mService) {
mService.broadcastIntentLocked(null, null, null, mIntent, null, this, 0, null, null,
- null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
+ null, null, AppOpsManager.OP_NONE, bOptions.toBundle(), true,
false, ActivityManagerService.MY_PID,
Process.SYSTEM_UID, Binder.getCallingUid(), Binder.getCallingPid(), mUserId);
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a5d0e72..ba3e1fb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2936,8 +2936,8 @@
// TODO b/64165549 Verify that mLock is not held before calling AMS methods
synchronized (mService) {
return mService.broadcastIntentLocked(null, null, null, intent, resolvedType,
- resultTo, resultCode, resultData, resultExtras, requiredPermissions, appOp,
- bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
+ resultTo, resultCode, resultData, resultExtras, requiredPermissions, null,
+ appOp, bOptions, ordered, sticky, callingPid, callingUid, realCallingUid,
realCallingPid, userId);
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b103def..b7ef10a 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -199,13 +199,12 @@
@Override
public void onPropertiesChanged(Properties properties) {
synchronized (mDeviceConfigLock) {
- for (String key : properties.getKeyset()) {
+ for (final String packageName : properties.getKeyset()) {
try {
// Check if the package is installed before caching it.
- final String packageName = keyToPackageName(key);
mPackageManager.getPackageInfo(packageName, 0);
final GamePackageConfiguration config =
- GamePackageConfiguration.fromProperties(key, properties);
+ GamePackageConfiguration.fromProperties(packageName, properties);
if (config.isValid()) {
putConfig(config);
} else {
@@ -290,8 +289,8 @@
private final String mPackageName;
private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs;
- private GamePackageConfiguration(String keyName) {
- mPackageName = keyToPackageName(keyName);
+ private GamePackageConfiguration(String packageName) {
+ mPackageName = packageName;
mModeConfigs = new ArrayMap<>();
}
@@ -563,9 +562,9 @@
}
}
- private void loadDeviceConfigLocked() {
+ void loadDeviceConfigLocked() {
final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0);
- final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName))
+ final String[] packageNames = packages.stream().map(e -> e.packageName)
.toArray(String[]::new);
synchronized (mDeviceConfigLock) {
final Properties properties = DeviceConfig.getProperties(
@@ -680,8 +679,7 @@
case ACTION_PACKAGE_CHANGED:
synchronized (mDeviceConfigLock) {
Properties properties = DeviceConfig.getProperties(
- DeviceConfig.NAMESPACE_GAME_OVERLAY,
- packageNameToKey(packageName));
+ DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName);
for (String key : properties.getKeyset()) {
GamePackageConfiguration config =
GamePackageConfiguration.fromProperties(key,
@@ -692,7 +690,9 @@
break;
case ACTION_PACKAGE_REMOVED:
disableCompatScale(packageName);
- mConfigs.remove(packageName);
+ synchronized (mDeviceConfigLock) {
+ mConfigs.remove(packageName);
+ }
break;
default:
// do nothing
@@ -710,23 +710,6 @@
mDeviceConfigListener = new DeviceConfigListener();
}
- /**
- * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use
- * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing
- * sequence of '_', then we replace all '.' chars with '_';
- */
- private static String packageNameToKey(String name) {
- return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_");
- }
-
- /**
- * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the
- * resulting special case '_.' with just '_' to get the original package name.
- */
- private static String keyToPackageName(String key) {
- return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_");
- }
-
private String dumpDeviceConfigs() {
StringBuilder out = new StringBuilder();
for (String key : mConfigs.keySet()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 419e686..3f07572 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1827,7 +1827,8 @@
}
}
- mHistoricalRegistry.clearHistory(uid, packageName);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
}
public void uidRemoved(int uid) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 282a12d..96bb73f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -503,7 +503,7 @@
}
}
- private static final class BtDeviceConnectionInfo {
+ /*package*/ static final class BtDeviceConnectionInfo {
final @NonNull BluetoothDevice mDevice;
final @AudioService.BtProfileConnectionState int mState;
final int mProfile;
@@ -520,6 +520,14 @@
mVolume = vol;
}
+ BtDeviceConnectionInfo(@NonNull BtDeviceConnectionInfo info) {
+ mDevice = info.mDevice;
+ mState = info.mState;
+ mProfile = info.mProfile;
+ mSupprNoisy = info.mSupprNoisy;
+ mVolume = info.mVolume;
+ }
+
// redefine equality op so we can match messages intended for this device
@Override
public boolean equals(Object o) {
@@ -541,18 +549,19 @@
}
}
- /*package*/ void postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
- int profile, boolean suppressNoisyIntent, int a2dpVolume) {
- final BtDeviceConnectionInfo info = new BtDeviceConnectionInfo(device, state, profile,
- suppressNoisyIntent, a2dpVolume);
-
- final String name = TextUtils.emptyIfNull(device.getName());
+ /**
+ * will block on mDeviceStateLock, which is held during an A2DP (dis) connection
+ * not just a simple message post
+ * @param info struct with the (dis)connection information
+ */
+ /*package*/ void queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ @NonNull BtDeviceConnectionInfo info) {
+ final String name = TextUtils.emptyIfNull(info.mDevice.getName());
new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
+ "postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent")
- .set(MediaMetrics.Property.STATE, state == BluetoothProfile.STATE_CONNECTED
+ .set(MediaMetrics.Property.STATE, info.mState == BluetoothProfile.STATE_CONNECTED
? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
- .set(MediaMetrics.Property.INDEX, a2dpVolume)
+ .set(MediaMetrics.Property.INDEX, info.mVolume)
.set(MediaMetrics.Property.NAME, name)
.record();
@@ -562,10 +571,10 @@
// when receiving a request to change the connection state of a device, this last
// request is the source of truth, so cancel all previous requests that are already in
// the handler
- removeScheduledA2dpEvents(device);
+ removeScheduledA2dpEvents(info.mDevice);
sendLMsgNoDelay(
- state == BluetoothProfile.STATE_CONNECTED
+ info.mState == BluetoothProfile.STATE_CONNECTED
? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
: MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
SENDMSG_QUEUE, info);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9707ace..edacd40 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -310,6 +310,8 @@
private static final int MSG_UPDATE_A11Y_SERVICE_UIDS = 35;
private static final int MSG_UPDATE_AUDIO_MODE = 36;
private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
+ private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
+ private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -6114,7 +6116,7 @@
* See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
*/
public void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
- @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
+ @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
int profile, boolean suppressNoisyIntent, int a2dpVolume) {
if (device == null) {
throw new IllegalArgumentException("Illegal null device");
@@ -6124,8 +6126,13 @@
throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
+ " (dis)connection, got " + state);
}
- mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device, state,
- profile, suppressNoisyIntent, a2dpVolume);
+
+ AudioDeviceBroker.BtDeviceConnectionInfo info =
+ new AudioDeviceBroker.BtDeviceConnectionInfo(device, state,
+ profile, suppressNoisyIntent, a2dpVolume);
+ sendMsg(mAudioHandler, MSG_SET_A2DP_DEV_CONNECTION_STATE, SENDMSG_QUEUE,
+ 0 /*arg1*/, 0 /*arg2*/,
+ /*obj*/ info, 0 /*delay*/);
}
/** only public for mocking/spying, do not call outside of AudioService */
@@ -6143,7 +6150,8 @@
if (device == null) {
throw new IllegalArgumentException("Illegal null device");
}
- mDeviceBroker.postBluetoothA2dpDeviceConfigChange(device);
+ sendMsg(mAudioHandler, MSG_A2DP_DEV_CONFIG_CHANGE, SENDMSG_QUEUE, 0, 0,
+ /*obj*/ device, /*delay*/ 0);
}
private static final Set<Integer> DEVICE_MEDIA_UNMUTED_ON_PLUG_SET;
@@ -7505,6 +7513,15 @@
onUpdateAudioMode(msg.arg1, msg.arg2, (String) msg.obj, false /*force*/);
}
break;
+
+ case MSG_SET_A2DP_DEV_CONNECTION_STATE:
+ mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ (AudioDeviceBroker.BtDeviceConnectionInfo) msg.obj);
+ break;
+
+ case MSG_A2DP_DEV_CONFIG_CHANGE:
+ mDeviceBroker.postBluetoothA2dpDeviceConfigChange((BluetoothDevice) msg.obj);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index c57d5af..52e8edf 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -454,8 +454,10 @@
}
final BluetoothDevice btDevice = deviceList.get(0);
// the device is guaranteed CONNECTED
- mDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(btDevice,
- BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK, true, -1);
+ mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ new AudioDeviceBroker.BtDeviceConnectionInfo(btDevice,
+ BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK,
+ true, -1));
}
/*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index cb7c568..a546a60 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -35,7 +35,6 @@
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -51,6 +50,7 @@
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
@@ -338,18 +338,31 @@
private static final boolean DEFAULT_APP_ENABLED = true;
private static final boolean DEFAULT_ALWAYS_REQUIRE_CONFIRMATION = false;
+ // Some devices that shipped before S already have face-specific settings. Instead of
+ // migrating, which is complicated, let's just keep using the existing settings.
+ private final boolean mUseLegacyFaceOnlySettings;
+
+ // Only used for legacy face-only devices
private final Uri FACE_UNLOCK_KEYGUARD_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED);
private final Uri FACE_UNLOCK_APP_ENABLED =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_APP_ENABLED);
+
+ // Continues to be used, even though it's face-specific.
private final Uri FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION =
Settings.Secure.getUriFor(Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION);
+ // Used for all devices other than legacy face-only devices
+ private final Uri BIOMETRIC_KEYGUARD_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED);
+ private final Uri BIOMETRIC_APP_ENABLED =
+ Settings.Secure.getUriFor(Settings.Secure.BIOMETRIC_APP_ENABLED);
+
private final ContentResolver mContentResolver;
private final List<BiometricService.EnabledOnKeyguardCallback> mCallbacks;
- private final Map<Integer, Boolean> mFaceEnabledOnKeyguard = new HashMap<>();
- private final Map<Integer, Boolean> mFaceEnabledForApps = new HashMap<>();
+ private final Map<Integer, Boolean> mBiometricEnabledOnKeyguard = new HashMap<>();
+ private final Map<Integer, Boolean> mBiometricEnabledForApps = new HashMap<>();
private final Map<Integer, Boolean> mFaceAlwaysRequireConfirmation = new HashMap<>();
/**
@@ -362,21 +375,44 @@
super(handler);
mContentResolver = context.getContentResolver();
mCallbacks = callbacks;
+
+ final boolean hasFingerprint = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
+ final boolean hasFace = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_FACE);
+
+ // Use the legacy setting on face-only devices that shipped on or before Q
+ mUseLegacyFaceOnlySettings =
+ Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
+ && hasFace && !hasFingerprint;
+
updateContentObserver();
}
public void updateContentObserver() {
mContentResolver.unregisterContentObserver(this);
- mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
- false /* notifyForDescendents */,
- this /* observer */,
- UserHandle.USER_ALL);
- mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
- false /* notifyForDescendents */,
- this /* observer */,
- UserHandle.USER_ALL);
+
+ if (mUseLegacyFaceOnlySettings) {
+ mContentResolver.registerContentObserver(FACE_UNLOCK_KEYGUARD_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(FACE_UNLOCK_APP_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ } else {
+ mContentResolver.registerContentObserver(BIOMETRIC_KEYGUARD_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ mContentResolver.registerContentObserver(BIOMETRIC_APP_ENABLED,
+ false /* notifyForDescendants */,
+ this /* observer */,
+ UserHandle.USER_ALL);
+ }
mContentResolver.registerContentObserver(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
- false /* notifyForDescendents */,
+ false /* notifyForDescendants */,
this /* observer */,
UserHandle.USER_ALL);
}
@@ -384,7 +420,7 @@
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
if (FACE_UNLOCK_KEYGUARD_ENABLED.equals(uri)) {
- mFaceEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+ mBiometricEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
mContentResolver,
Settings.Secure.FACE_UNLOCK_KEYGUARD_ENABLED,
DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
@@ -394,7 +430,7 @@
notifyEnabledOnKeyguardCallbacks(userId);
}
} else if (FACE_UNLOCK_APP_ENABLED.equals(uri)) {
- mFaceEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+ mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
mContentResolver,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
@@ -405,22 +441,45 @@
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
DEFAULT_ALWAYS_REQUIRE_CONFIRMATION ? 1 : 0 /* default */,
userId) != 0);
+ } else if (BIOMETRIC_KEYGUARD_ENABLED.equals(uri)) {
+ mBiometricEnabledOnKeyguard.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
+ DEFAULT_KEYGUARD_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
+
+ if (userId == ActivityManager.getCurrentUser() && !selfChange) {
+ notifyEnabledOnKeyguardCallbacks(userId);
+ }
+ } else if (BIOMETRIC_APP_ENABLED.equals(uri)) {
+ mBiometricEnabledForApps.put(userId, Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.BIOMETRIC_APP_ENABLED,
+ DEFAULT_APP_ENABLED ? 1 : 0 /* default */,
+ userId) != 0);
}
}
- public boolean getFaceEnabledOnKeyguard() {
- final int user = ActivityManager.getCurrentUser();
- if (!mFaceEnabledOnKeyguard.containsKey(user)) {
- onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, user);
+ public boolean getEnabledOnKeyguard(int userId) {
+ if (!mBiometricEnabledOnKeyguard.containsKey(userId)) {
+ if (mUseLegacyFaceOnlySettings) {
+ onChange(true /* selfChange */, FACE_UNLOCK_KEYGUARD_ENABLED, userId);
+ } else {
+ onChange(true /* selfChange */, BIOMETRIC_KEYGUARD_ENABLED, userId);
+ }
}
- return mFaceEnabledOnKeyguard.get(user);
+ return mBiometricEnabledOnKeyguard.get(userId);
}
- public boolean getFaceEnabledForApps(int userId) {
- if (!mFaceEnabledForApps.containsKey(userId)) {
- onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+ public boolean getEnabledForApps(int userId) {
+ if (!mBiometricEnabledForApps.containsKey(userId)) {
+ if (mUseLegacyFaceOnlySettings) {
+ onChange(true /* selfChange */, FACE_UNLOCK_APP_ENABLED, userId);
+ } else {
+ onChange(true /* selfChange */, BIOMETRIC_APP_ENABLED, userId);
+ }
}
- return mFaceEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
+ return mBiometricEnabledForApps.getOrDefault(userId, DEFAULT_APP_ENABLED);
}
public boolean getConfirmationAlwaysRequired(@BiometricAuthenticator.Modality int modality,
@@ -442,8 +501,8 @@
void notifyEnabledOnKeyguardCallbacks(int userId) {
List<EnabledOnKeyguardCallback> callbacks = mCallbacks;
for (int i = 0; i < callbacks.size(); i++) {
- callbacks.get(i).notify(BiometricSourceType.FACE,
- mFaceEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
+ callbacks.get(i).notify(
+ mBiometricEnabledOnKeyguard.getOrDefault(userId, DEFAULT_KEYGUARD_ENABLED),
userId);
}
}
@@ -462,9 +521,9 @@
}
}
- void notify(BiometricSourceType sourceType, boolean enabled, int userId) {
+ void notify(boolean enabled, int userId) {
try {
- mCallback.onChanged(sourceType, enabled, userId);
+ mCallback.onChanged(enabled, userId);
} catch (DeadObjectException e) {
Slog.w(TAG, "Death while invoking notify", e);
mEnabledOnKeyguardCallbacks.remove(this);
@@ -747,8 +806,8 @@
mEnabledOnKeyguardCallbacks.add(new EnabledOnKeyguardCallback(callback));
try {
- callback.onChanged(BiometricSourceType.FACE,
- mSettingObserver.getFaceEnabledOnKeyguard(), callingUserId);
+ callback.onChanged(mSettingObserver.getEnabledOnKeyguard(callingUserId),
+ callingUserId);
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception", e);
}
@@ -1347,6 +1406,9 @@
}
private void dumpInternal(PrintWriter pw) {
+ pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings);
+ pw.println();
+
pw.println("Sensors:");
for (BiometricSensor sensor : mSensors) {
pw.println(" " + sensor);
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 262cb36..c4bd18b 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -197,17 +197,7 @@
private static boolean isEnabledForApp(BiometricService.SettingObserver settingObserver,
@BiometricAuthenticator.Modality int modality, int userId) {
- switch (modality) {
- case TYPE_FINGERPRINT:
- return true;
- case TYPE_IRIS:
- return true;
- case TYPE_FACE:
- return settingObserver.getFaceEnabledForApps(userId);
- default:
- Slog.w(TAG, "Unsupported modality: " + modality);
- return false;
- }
+ return settingObserver.getEnabledForApps(userId);
}
private static boolean isBiometricDisabledByDevicePolicy(
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 d56fd12..b795b54 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
@@ -375,12 +375,12 @@
}
@Override
- public void onFeaturesRetrieved(byte[] features, int enrollmentId) {
+ public void onFeaturesRetrieved(byte[] features) {
}
@Override
- public void onFeatureSet(int enrollmentId, byte feature) {
+ public void onFeatureSet(byte feature) {
}
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 c63af7e..f1bfd53 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
@@ -32,6 +32,7 @@
*/
public class TestHal extends IFace.Stub {
private static final String TAG = "face.aidl.TestHal";
+
@Override
public SensorProps[] getSensorProps() {
Slog.w(TAG, "getSensorProps");
@@ -102,16 +103,16 @@
}
@Override
- public void getFeatures(int enrollmentId) throws RemoteException {
+ public void getFeatures() throws RemoteException {
Slog.w(TAG, "getFeatures");
- cb.onFeaturesRetrieved(new byte[0], enrollmentId);
+ cb.onFeaturesRetrieved(new byte[0]);
}
@Override
- public void setFeature(HardwareAuthToken hat, int enrollmentId,
- byte feature, boolean enabled) throws RemoteException {
+ public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled)
+ throws RemoteException {
Slog.w(TAG, "setFeature");
- cb.onFeatureSet(enrollmentId, feature);
+ cb.onFeatureSet(feature);
}
@Override
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 1bbcede..b2d35f4 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -15,9 +15,6 @@
*/
package com.android.server.camera;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
-import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.os.Build.VERSION_CODES.M;
import android.annotation.IntDef;
@@ -27,15 +24,12 @@
import android.app.ActivityTaskManager;
import android.app.TaskStackListener;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.graphics.Rect;
import android.hardware.CameraSessionStats;
import android.hardware.CameraStreamStats;
import android.hardware.ICameraService;
@@ -43,7 +37,6 @@
import android.hardware.camera2.CameraMetadata;
import android.hardware.display.DisplayManager;
import android.media.AudioManager;
-import android.metrics.LogMaker;
import android.nfc.INfcAdapter;
import android.os.Binder;
import android.os.Handler;
@@ -60,17 +53,16 @@
import android.util.Log;
import android.util.Slog;
import android.view.Display;
+import android.view.IDisplayWindowListener;
import android.view.Surface;
+import android.view.WindowManagerGlobal;
import com.android.internal.annotations.GuardedBy;
import com.android.framework.protobuf.nano.MessageNano;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
-import com.android.server.SystemService.TargetUser;
import com.android.server.wm.WindowManagerInternal;
import java.lang.annotation.Retention;
@@ -223,6 +215,37 @@
}
}
+ private final class DisplayWindowListener extends IDisplayWindowListener.Stub {
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ ICameraService cs = getCameraServiceRawLocked();
+ if (cs == null) return;
+
+ try {
+ cs.notifyDisplayConfigurationChange();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e);
+ // Not much we can do if camera service is dead.
+ }
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onFixedRotationStarted(int displayId, int newRotation) { }
+
+ @Override
+ public void onFixedRotationFinished(int displayId) { }
+ }
+
+
+ private final DisplayWindowListener mDisplayWindowListener = new DisplayWindowListener();
+
private final TaskStateHandler mTaskStackListener = new TaskStateHandler();
private final class TaskInfo {
@@ -236,13 +259,12 @@
private final class TaskStateHandler extends TaskStackListener {
private final Object mMapLock = new Object();
- // maps the current top level task id to its corresponding package name
+ // maps the package name to its corresponding current top level task id
@GuardedBy("mMapLock")
private final ArrayMap<String, TaskInfo> mTaskInfoMap = new ArrayMap<>();
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
- throws RemoteException {
+ public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
synchronized (mMapLock) {
TaskInfo info = new TaskInfo();
info.frontTaskId = taskInfo.taskId;
@@ -257,7 +279,7 @@
}
@Override
- public void onTaskRemoved(int taskId) throws RemoteException {
+ public void onTaskRemoved(int taskId) {
synchronized (mMapLock) {
for (Map.Entry<String, TaskInfo> entry : mTaskInfoMap.entrySet()){
if (entry.getValue().frontTaskId == taskId) {
@@ -319,7 +341,7 @@
/**
* Gets whether crop-rotate-scale is needed.
*/
- private boolean getNeedCropRotateScale(Context ctx, String packageName,
+ private boolean getNeedCropRotateScale(@NonNull Context ctx, @NonNull String packageName,
@Nullable TaskInfo taskInfo, int sensorOrientation, int lensFacing) {
if (taskInfo == null) {
return false;
@@ -334,7 +356,7 @@
// Only enable the crop-rotate-scale workaround if the app targets M or below and is not
// resizeable.
- if ((ctx != null) && !isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
+ if (!isMOrBelow(ctx, packageName) && taskInfo.isResizeable) {
Slog.v(TAG,
"The activity is N or above and claims to support resizeable-activity. "
+ "Crop-rotate-scale is disabled.");
@@ -372,12 +394,8 @@
taskInfo.isFixedOrientationLandscape);
// We need to do crop-rotate-scale when camera is landscape and activity is portrait or
// vice versa.
- if ((taskInfo.isFixedOrientationPortrait && landscapeCamera)
- || (taskInfo.isFixedOrientationLandscape && !landscapeCamera)) {
- return true;
- } else {
- return false;
- }
+ return (taskInfo.isFixedOrientationPortrait && landscapeCamera)
+ || (taskInfo.isFixedOrientationLandscape && !landscapeCamera);
}
@Override
@@ -389,14 +407,10 @@
return false;
}
- // A few remaining todos:
- // 1) Do the same check when working in WM compatible mode. The sequence needs
- // to be adjusted and use orientation events as triggers for all active camera
- // clients.
- // 2) Modify the sensor orientation in camera characteristics along with any 3A regions
- // in capture requests/results to account for thea physical rotation. The former
- // is somewhat tricky as it assumes that camera clients always check for the current
- // value by retrieving the camera characteristics from the camera device.
+ // TODO: Modify the sensor orientation in camera characteristics along with any 3A
+ // regions in capture requests/results to account for thea physical rotation. The
+ // former is somewhat tricky as it assumes that camera clients always check for the
+ // current value by retrieving the camera characteristics from the camera device.
return getNeedCropRotateScale(mContext, packageName,
mTaskStackListener.getFrontTaskInfo(packageName), sensorOrientation,
lensFacing);
@@ -522,18 +536,25 @@
publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy);
publishLocalService(CameraServiceProxy.class, this);
-
- try {
- ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to register task stack listener!");
- }
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
CameraStatsJobService.schedule(mContext);
+
+ try {
+ ActivityTaskManager.getService().registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register task stack listener!");
+ }
+
+ try {
+ WindowManagerGlobal.getWindowManagerService().registerDisplayWindowListener(
+ mDisplayWindowListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register display window listener!");
+ }
}
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4d310cb..584174e 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -59,6 +59,7 @@
import com.android.server.ConnectivityService;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
@@ -281,6 +282,9 @@
*/
public static final int ARG_AGENT_SUCCESS = 1;
+ // How long this network should linger for.
+ private int mLingerDurationMs;
+
// All inactivity timers for this network, sorted by expiry time. A timer is added whenever
// a request is moved to a network with a better score, regardless of whether the network is or
// was lingering or not. An inactivity timer is also added when a network connects
@@ -349,7 +353,8 @@
@NonNull NetworkScore score, Context context,
Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, int factorySerialNumber, int creatorUid,
- QosCallbackTracker qosCallbackTracker, ConnectivityService.Dependencies deps) {
+ int lingerDurationMs, QosCallbackTracker qosCallbackTracker,
+ ConnectivityService.Dependencies deps) {
Objects.requireNonNull(net);
Objects.requireNonNull(info);
Objects.requireNonNull(lp);
@@ -370,6 +375,7 @@
mHandler = handler;
this.factorySerialNumber = factorySerialNumber;
this.creatorUid = creatorUid;
+ mLingerDurationMs = lingerDurationMs;
mQosCallbackTracker = qosCallbackTracker;
}
@@ -685,6 +691,12 @@
mHandler.obtainMessage(NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED,
teardownDelayMs, 0, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
}
+
+ @Override
+ public void sendLingerDuration(final int durationMs) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_LINGER_DURATION_CHANGED,
+ new Pair<>(NetworkAgentInfo.this, durationMs)).sendToTarget();
+ }
}
/**
@@ -954,13 +966,14 @@
/**
* Sets the specified requestId to linger on this network for the specified time. Called by
- * ConnectivityService when the request is moved to another network with a higher score, or
+ * ConnectivityService when any request is moved to another network with a higher score, or
* when a network is newly created.
*
* @param requestId The requestId of the request that no longer need to be served by this
* network. Or {@link NetworkRequest.REQUEST_ID_NONE} if this is the
- * {@code LingerTimer} for a newly created network.
+ * {@code InactivityTimer} for a newly created network.
*/
+ // TODO: Consider creating a dedicated function for nascent network, e.g. start/stopNascent.
public void lingerRequest(int requestId, long now, long duration) {
if (mInactivityTimerForRequest.get(requestId) != null) {
// Cannot happen. Once a request is lingering on a particular network, we cannot
@@ -976,6 +989,19 @@
}
/**
+ * Sets the specified requestId to linger on this network for the timeout set when
+ * initializing or modified by {@link #setLingerDuration(int)}. Called by
+ * ConnectivityService when any request is moved to another network with a higher score.
+ *
+ * @param requestId The requestId of the request that no longer need to be served by this
+ * network.
+ * @param now current system timestamp obtained by {@code SystemClock.elapsedRealtime}.
+ */
+ public void lingerRequest(int requestId, long now) {
+ lingerRequest(requestId, now, mLingerDurationMs);
+ }
+
+ /**
* Cancel lingering. Called by ConnectivityService when a request is added to this network.
* Returns true if the given requestId was lingering on this network, false otherwise.
*/
@@ -1012,6 +1038,7 @@
}
if (newExpiry > 0) {
+ // If the newExpiry timestamp is in the past, the wakeup message will fire immediately.
mInactivityMessage = new WakeupMessage(
mContext, mHandler,
"NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */,
@@ -1041,8 +1068,33 @@
}
/**
- * Return whether the network is just connected and about to be torn down because of not
- * satisfying any request.
+ * Set the linger duration for this NAI.
+ * @param durationMs The new linger duration, in milliseconds.
+ */
+ public void setLingerDuration(final int durationMs) {
+ final long diff = durationMs - mLingerDurationMs;
+ final ArrayList<InactivityTimer> newTimers = new ArrayList<>();
+ for (final InactivityTimer timer : mInactivityTimers) {
+ if (timer.requestId == NetworkRequest.REQUEST_ID_NONE) {
+ // Don't touch nascent timer, re-add as is.
+ newTimers.add(timer);
+ } else {
+ newTimers.add(new InactivityTimer(timer.requestId, timer.expiryMs + diff));
+ }
+ }
+ mInactivityTimers.clear();
+ mInactivityTimers.addAll(newTimers);
+ updateInactivityTimer();
+ mLingerDurationMs = durationMs;
+ }
+
+ /**
+ * Return whether the network satisfies no request, but is still being kept up
+ * because it has just connected less than
+ * {@code ConnectivityService#DEFAULT_NASCENT_DELAY_MS}ms ago and is thus still considered
+ * nascent. Note that nascent mechanism uses inactivity timer which isn't
+ * associated with a request. Thus, use {@link NetworkRequest#REQUEST_ID_NONE} to identify it.
+ *
*/
public boolean isNascent() {
return mInactive && mInactivityTimers.size() == 1
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed8ea18..7994fcc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4100,13 +4100,10 @@
}
@Override
- public void removeImeSurfaceFromWindow(IBinder windowToken,
- IVoidResultCallback resultCallback) {
- CallbackUtils.onResult(resultCallback, () -> {
- // No permission check, because we'll only execute the request if the calling window is
- // also the current IME client.
- mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
- });
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
+ // No permission check, because we'll only execute the request if the calling window is
+ // also the current IME client.
+ mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget();
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index ef1489b..6244743 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -1510,10 +1510,8 @@
@BinderThread
@Override
- public void removeImeSurfaceFromWindow(IBinder windowToken,
- IVoidResultCallback resultCallback) {
+ public void removeImeSurfaceFromWindowAsync(IBinder windowToken) {
reportNotSupported();
- CallbackUtils.onResult(resultCallback, () -> { });
}
@BinderThread
diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
index 045e06d0..2ffc62a 100644
--- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
+++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java
@@ -178,8 +178,9 @@
}
/** Logs that a provider has entered or exited stationary throttling. */
- public void logProviderStationaryThrottled(String provider, boolean throttled) {
- addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled);
+ public void logProviderStationaryThrottled(String provider, boolean throttled,
+ ProviderRequest request) {
+ addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled, request);
}
/** Logs that the location power save mode has changed. */
@@ -217,7 +218,7 @@
(Integer) args[1], (CallerIdentity) args[2]);
case EVENT_PROVIDER_STATIONARY_THROTTLED:
return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0],
- (Boolean) args[1]);
+ (Boolean) args[1], (ProviderRequest) args[2]);
case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE:
return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]);
default:
@@ -355,17 +356,19 @@
private static final class ProviderStationaryThrottledEvent extends ProviderEvent {
private final boolean mStationaryThrottled;
+ private final ProviderRequest mRequest;
ProviderStationaryThrottledEvent(long timeDelta, String provider,
- boolean stationaryThrottled) {
+ boolean stationaryThrottled, ProviderRequest request) {
super(timeDelta, provider);
mStationaryThrottled = stationaryThrottled;
+ mRequest = request;
}
@Override
public String getLogString() {
return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled"
- : "unthrottled");
+ : "unthrottled") + ", request = " + mRequest;
}
}
diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
index ab7e526..22a675a 100644
--- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java
@@ -206,7 +206,7 @@
if (D) {
Log.d(TAG, mName + " provider stationary throttled");
}
- EVENT_LOG.logProviderStationaryThrottled(mName, true);
+ EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest);
}
if (mDeliverLastLocationCallback != null) {
@@ -224,7 +224,7 @@
}
} else {
if (oldThrottlingIntervalMs != INTERVAL_DISABLED) {
- EVENT_LOG.logProviderStationaryThrottled(mName, false);
+ EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest);
if (D) {
Log.d(TAG, mName + " provider stationary unthrottled");
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index abcf4fb..b10d56b 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -384,7 +384,7 @@
if (mPlaybackState == null) {
return false;
}
- return mPlaybackState.isActive() == expected;
+ return mPlaybackState.isActiveState() == expected;
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 8bd3b1e..42b7c9d3 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -100,7 +100,7 @@
IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
deletionFilter.addDataScheme(SCHEME_DELETION);
- mContext.registerReceiver(mFileCleaupReceiver, deletionFilter);
+ mContext.registerReceiver(mFileCleanupReceiver, deletionFilter);
}
public void init() {
@@ -273,13 +273,36 @@
}
}
+ /**
+ * Remove the first entry from the list of history files whose file matches the given file path.
+ *
+ * This method is necessary for anything that only has an absolute file path rather than an
+ * AtomicFile object from the list of history files.
+ *
+ * filePath should be an absolute path.
+ */
+ void removeFilePathFromHistory(String filePath) {
+ if (filePath == null) {
+ return;
+ }
+
+ Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator();
+ while (historyFileItr.hasNext()) {
+ final AtomicFile af = historyFileItr.next();
+ if (af != null && filePath.equals(af.getBaseFile().getAbsolutePath())) {
+ historyFileItr.remove();
+ return;
+ }
+ }
+ }
+
private void deleteFile(AtomicFile file) {
if (DEBUG) {
Slog.d(TAG, "Removed " + file.getBaseFile().getName());
}
file.delete();
// TODO: delete all relevant bitmaps, once they exist
- mHistoryFiles.remove(file);
+ removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
}
private void scheduleDeletion(File file, long creationTime, int retentionDays) {
@@ -342,7 +365,7 @@
}
}
- private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -358,7 +381,7 @@
Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
}
fileToDelete.delete();
- mHistoryFiles.remove(fileToDelete);
+ removeFilePathFromHistory(filePath);
}
} catch (Exception e) {
Slog.e(TAG, "Failed to delete notification history file", e);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3ddfe6c..1e6e5cb 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -380,8 +380,6 @@
static final int INVALID_UID = -1;
static final String ROOT_PKG = "root";
- static final boolean ENABLE_BLOCKED_TOASTS = true;
-
static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] {
Adjustment.KEY_CONTEXTUAL_ACTIONS,
Adjustment.KEY_TEXT_REPLIES,
@@ -3165,34 +3163,11 @@
}
final int callingUid = Binder.getCallingUid();
- final UserHandle callingUser = Binder.getCallingUserHandle();
+ checkCallerIsSameApp(pkg);
final boolean isSystemToast = isCallerSystemOrPhone()
|| PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
- final boolean isPackageSuspended = isPackagePaused(pkg);
- final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
- callingUid);
-
- final boolean appIsForeground;
- final long callingIdentity = Binder.clearCallingIdentity();
- try {
- appIsForeground = mActivityManager.getUidImportance(callingUid)
- == IMPORTANCE_FOREGROUND;
- } finally {
- Binder.restoreCallingIdentity(callingIdentity);
- }
-
- if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage
- && !appIsForeground) || isPackageSuspended)) {
- Slog.e(TAG, "Suppressing toast from package " + pkg
- + (isPackageSuspended ? " due to package suspended."
- : " by user request."));
- return;
- }
-
boolean isAppRenderedToast = (callback != null);
- if (blockToast(callingUid, isSystemToast, isAppRenderedToast)) {
- Slog.w(TAG, "Blocking custom toast from package " + pkg
- + " due to package not in the foreground at time the toast was posted");
+ if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) {
return;
}
@@ -3246,6 +3221,39 @@
}
}
+ private boolean checkCanEnqueueToast(String pkg, int callingUid,
+ boolean isAppRenderedToast, boolean isSystemToast) {
+ final boolean isPackageSuspended = isPackagePaused(pkg);
+ final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg,
+ callingUid);
+
+ final boolean appIsForeground;
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ appIsForeground = mActivityManager.getUidImportance(callingUid)
+ == IMPORTANCE_FOREGROUND;
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+
+ if (!isSystemToast && ((notificationsDisabledForPackage && !appIsForeground)
+ || isPackageSuspended)) {
+ Slog.e(TAG, "Suppressing toast from package " + pkg
+ + (isPackageSuspended ? " due to package suspended."
+ : " by user request."));
+ return false;
+ }
+
+ if (blockToast(callingUid, isSystemToast, isAppRenderedToast,
+ isPackageInForegroundForToast(callingUid))) {
+ Slog.w(TAG, "Blocking custom toast from package " + pkg
+ + " due to package not in the foreground at time the toast was posted");
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public void cancelToast(String pkg, IBinder token) {
Slog.i(TAG, "cancelToast pkg=" + pkg + " token=" + token);
@@ -7692,12 +7700,13 @@
boolean isWithinQuota =
mToastRateLimiter.isWithinQuota(userId, record.pkg, TOAST_QUOTA_TAG)
|| isExemptFromRateLimiting(record.pkg, userId);
+ boolean isPackageInForeground = isPackageInForegroundForToast(record.uid);
if (tryShowToast(
- record, rateLimitingEnabled, isWithinQuota)) {
+ record, rateLimitingEnabled, isWithinQuota, isPackageInForeground)) {
scheduleDurationReachedLocked(record, lastToastWasTextRecord);
mIsCurrentToastShown = true;
- if (rateLimitingEnabled) {
+ if (rateLimitingEnabled && !isPackageInForeground) {
mToastRateLimiter.noteEvent(userId, record.pkg, TOAST_QUOTA_TAG);
}
return;
@@ -7713,14 +7722,15 @@
/** Returns true if it successfully showed the toast. */
private boolean tryShowToast(ToastRecord record, boolean rateLimitingEnabled,
- boolean isWithinQuota) {
- if (rateLimitingEnabled && !isWithinQuota) {
+ boolean isWithinQuota, boolean isPackageInForeground) {
+ if (rateLimitingEnabled && !isWithinQuota && !isPackageInForeground) {
reportCompatRateLimitingToastsChange(record.uid);
Slog.w(TAG, "Package " + record.pkg + " is above allowed toast quota, the "
+ "following toast was blocked and discarded: " + record);
return false;
}
- if (blockToast(record.uid, record.isSystemToast, record.isAppRendered())) {
+ if (blockToast(record.uid, record.isSystemToast, record.isAppRendered(),
+ isPackageInForeground)) {
Slog.w(TAG, "Blocking custom toast from package " + record.pkg
+ " due to package not in the foreground at the time of showing the toast");
return false;
@@ -7911,10 +7921,11 @@
* with targetSdk < R. For apps with targetSdk R+, text toasts are not app-rendered, so
* isAppRenderedToast == true means it's a custom toast.
*/
- private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast) {
+ private boolean blockToast(int uid, boolean isSystemToast, boolean isAppRenderedToast,
+ boolean isPackageInForeground) {
return isAppRenderedToast
&& !isSystemToast
- && !isPackageInForegroundForToast(uid)
+ && !isPackageInForeground
&& CompatChanges.isChangeEnabled(CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, uid);
}
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
index a9dbb2f..94a1f3b 100644
--- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -28,10 +30,10 @@
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.AndroidPackage;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.Map;
import java.util.Set;
@@ -84,14 +86,15 @@
* See class comment for specific types.
*/
@GuardedBy("mLock")
- private final Map<String, Map<String, Set<String>>> mActorToTargetToOverlays = new HashMap<>();
+ private final ArrayMap<String, ArrayMap<String, ArraySet<String>>> mActorToTargetToOverlays =
+ new ArrayMap<>();
/**
* Keys are actor package names, values are generic package names the actor should be able
* to see.
*/
@GuardedBy("mLock")
- private final Map<String, Set<String>> mActorPkgToPkgs = new HashMap<>();
+ private final ArrayMap<String, Set<String>> mActorPkgToPkgs = new ArrayMap<>();
@GuardedBy("mLock")
private boolean mDeferRebuild;
@@ -160,21 +163,26 @@
*
* @param pkg the package to add
* @param otherPkgs map of other packages to consider, excluding {@param pkg}
+ * @return Set of packages that may have changed visibility
*/
- public void addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
+ public ArraySet<String> addPkg(AndroidPackage pkg, Map<String, AndroidPackage> otherPkgs) {
synchronized (mLock) {
+ ArraySet<String> changed = new ArraySet<>();
+
if (!pkg.getOverlayables().isEmpty()) {
- addTarget(pkg, otherPkgs);
+ addTarget(pkg, otherPkgs, changed);
}
// TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
- addOverlay(pkg, otherPkgs);
+ addOverlay(pkg, otherPkgs, changed);
}
if (!mDeferRebuild) {
rebuild();
}
+
+ return changed;
}
}
@@ -184,27 +192,40 @@
* of {@link SystemConfig#getNamedActors()}.
*
* @param pkgName name to remove, as was added through {@link #addPkg(AndroidPackage, Map)}
+ * @return Set of packages that may have changed visibility
*/
- public void removePkg(String pkgName) {
+ public ArraySet<String> removePkg(String pkgName) {
synchronized (mLock) {
- removeTarget(pkgName);
- removeOverlay(pkgName);
+ ArraySet<String> changedPackages = new ArraySet<>();
+ removeTarget(pkgName, changedPackages);
+ removeOverlay(pkgName, changedPackages);
if (!mDeferRebuild) {
rebuild();
}
+
+ return changedPackages;
}
}
- private void removeTarget(String target) {
+ /**
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
+ */
+ private void removeTarget(String target, @NonNull Collection<String> changedPackages) {
synchronized (mLock) {
- Iterator<Map<String, Set<String>>> iterator =
- mActorToTargetToOverlays.values().iterator();
- while (iterator.hasNext()) {
- Map<String, Set<String>> next = iterator.next();
- next.remove(target);
- if (next.isEmpty()) {
- iterator.remove();
+ int size = mActorToTargetToOverlays.size();
+ for (int index = size - 1; index >= 0; index--) {
+ ArrayMap<String, ArraySet<String>> targetToOverlays =
+ mActorToTargetToOverlays.valueAt(index);
+ if (targetToOverlays.containsKey(target)) {
+ targetToOverlays.remove(target);
+
+ String actor = mActorToTargetToOverlays.keyAt(index);
+ changedPackages.add(mProvider.getActorPkg(actor));
+
+ if (targetToOverlays.isEmpty()) {
+ mActorToTargetToOverlays.removeAt(index);
+ }
}
}
}
@@ -215,16 +236,19 @@
*
* If a target overlays itself, it will not be associated with itself, as only one half of the
* relationship needs to exist for visibility purposes.
+ *
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
*/
- private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs) {
+ private void addTarget(AndroidPackage targetPkg, Map<String, AndroidPackage> otherPkgs,
+ @NonNull Collection<String> changedPackages) {
synchronized (mLock) {
String target = targetPkg.getPackageName();
- removeTarget(target);
+ removeTarget(target, changedPackages);
Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
for (String overlayable : overlayablesToActors.keySet()) {
String actor = overlayablesToActors.get(overlayable);
- addTargetToMap(actor, target);
+ addTargetToMap(actor, target, changedPackages);
for (AndroidPackage overlayPkg : otherPkgs.values()) {
Map<String, Set<String>> targetToOverlayables =
@@ -235,18 +259,38 @@
}
if (overlayables.contains(overlayable)) {
- addOverlayToMap(actor, target, overlayPkg.getPackageName());
+ String overlay = overlayPkg.getPackageName();
+ addOverlayToMap(actor, target, overlay, changedPackages);
}
}
}
}
}
- private void removeOverlay(String overlay) {
+ /**
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
+ */
+ private void removeOverlay(String overlay, @NonNull Collection<String> changedPackages) {
synchronized (mLock) {
- for (Map<String, Set<String>> targetToOverlays : mActorToTargetToOverlays.values()) {
- for (Set<String> overlays : targetToOverlays.values()) {
- overlays.remove(overlay);
+ int actorsSize = mActorToTargetToOverlays.size();
+ for (int actorIndex = actorsSize - 1; actorIndex >= 0; actorIndex--) {
+ ArrayMap<String, ArraySet<String>> targetToOverlays =
+ mActorToTargetToOverlays.valueAt(actorIndex);
+ int targetsSize = targetToOverlays.size();
+ for (int targetIndex = targetsSize - 1; targetIndex >= 0; targetIndex--) {
+ final Set<String> overlays = targetToOverlays.valueAt(targetIndex);
+
+ if (overlays.remove(overlay)) {
+ String actor = mActorToTargetToOverlays.keyAt(actorIndex);
+ changedPackages.add(mProvider.getActorPkg(actor));
+
+ // targetToOverlays should not be removed here even if empty as the actor
+ // will still have visibility to the target even if no overlays exist
+ }
+ }
+
+ if (targetToOverlays.isEmpty()) {
+ mActorToTargetToOverlays.removeAt(actorIndex);
}
}
}
@@ -257,11 +301,14 @@
*
* If an overlay targets itself, it will not be associated with itself, as only one half of the
* relationship needs to exist for visibility purposes.
+ *
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
*/
- private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs) {
+ private void addOverlay(AndroidPackage overlayPkg, Map<String, AndroidPackage> otherPkgs,
+ @NonNull Collection<String> changedPackages) {
synchronized (mLock) {
String overlay = overlayPkg.getPackageName();
- removeOverlay(overlay);
+ removeOverlay(overlay, changedPackages);
Map<String, Set<String>> targetToOverlayables =
mProvider.getTargetToOverlayables(overlayPkg);
@@ -280,7 +327,7 @@
if (TextUtils.isEmpty(actor)) {
continue;
}
- addOverlayToMap(actor, targetPkgName, overlay);
+ addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
}
}
}
@@ -312,7 +359,8 @@
continue;
}
- Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ ArrayMap<String, ArraySet<String>> targetToOverlays =
+ mActorToTargetToOverlays.get(actor);
Set<String> pkgs = new HashSet<>();
for (String target : targetToOverlays.keySet()) {
@@ -326,36 +374,51 @@
}
}
- private void addTargetToMap(String actor, String target) {
- Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ /**
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
+ */
+ private void addTargetToMap(String actor, String target,
+ @NonNull Collection<String> changedPackages) {
+ ArrayMap<String, ArraySet<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
if (targetToOverlays == null) {
- targetToOverlays = new HashMap<>();
+ targetToOverlays = new ArrayMap<>();
mActorToTargetToOverlays.put(actor, targetToOverlays);
}
- Set<String> overlays = targetToOverlays.get(target);
+ ArraySet<String> overlays = targetToOverlays.get(target);
if (overlays == null) {
- overlays = new HashSet<>();
+ overlays = new ArraySet<>();
targetToOverlays.put(target, overlays);
}
+
+ // For now, only actors themselves can gain or lose visibility through package changes
+ changedPackages.add(mProvider.getActorPkg(actor));
}
- private void addOverlayToMap(String actor, String target, String overlay) {
+ /**
+ * @param changedPackages Ongoing collection of packages that may have changed visibility
+ */
+ private void addOverlayToMap(String actor, String target, String overlay,
+ @NonNull Collection<String> changedPackages) {
synchronized (mLock) {
- Map<String, Set<String>> targetToOverlays = mActorToTargetToOverlays.get(actor);
+ ArrayMap<String, ArraySet<String>> targetToOverlays =
+ mActorToTargetToOverlays.get(actor);
if (targetToOverlays == null) {
- targetToOverlays = new HashMap<>();
+ targetToOverlays = new ArrayMap<>();
mActorToTargetToOverlays.put(actor, targetToOverlays);
}
- Set<String> overlays = targetToOverlays.get(target);
+ ArraySet<String> overlays = targetToOverlays.get(target);
if (overlays == null) {
- overlays = new HashSet<>();
+ overlays = new ArraySet<>();
targetToOverlays.put(target, overlays);
}
overlays.add(overlay);
}
+
+ // For now, only actors themselves can gain or lose visibility through package changes
+ changedPackages.add(mProvider.getActorPkg(actor));
}
public interface Provider {
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index ca8202f..4f527f2 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -662,11 +662,27 @@
removePackage(newPkgSetting);
}
mStateProvider.runWithState((settings, users) -> {
- addPackageInternal(newPkgSetting, settings);
+ ArraySet<String> additionalChangedPackages =
+ addPackageInternal(newPkgSetting, settings);
synchronized (mCacheLock) {
if (mShouldFilterCache != null) {
updateShouldFilterCacheForPackage(mShouldFilterCache, null, newPkgSetting,
settings, users, settings.size());
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageSetting changedPkgSetting = settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
+ }
+
+ updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+ changedPkgSetting, settings, users, settings.size());
+ }
+ }
} // else, rebuild entire cache when system is ready
}
});
@@ -676,7 +692,12 @@
}
}
- private void addPackageInternal(PackageSetting newPkgSetting,
+ /**
+ * @return Additional packages that may have had their viewing visibility changed and may need
+ * to be updated in the cache. Returns null if there are no additional packages.
+ */
+ @Nullable
+ private ArraySet<String> addPackageInternal(PackageSetting newPkgSetting,
ArrayMap<String, PackageSetting> existingSettings) {
if (Objects.equals("android", newPkgSetting.name)) {
// let's set aside the framework signatures
@@ -692,8 +713,7 @@
final AndroidPackage newPkg = newPkgSetting.pkg;
if (newPkg == null) {
- // nothing to add
- return;
+ return null;
}
if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) {
@@ -765,8 +785,13 @@
existingPkgs.put(pkgSetting.name, pkgSetting.pkg);
}
}
- mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
+ ArraySet<String> changedPackages =
+ mOverlayReferenceMapper.addPkg(newPkgSetting.pkg, existingPkgs);
+
mFeatureConfig.updatePackageState(newPkgSetting, false /*removed*/);
+
+ return changedPackages;
}
@GuardedBy("mCacheLock")
@@ -1080,7 +1105,9 @@
}
}
- mOverlayReferenceMapper.removePkg(setting.name);
+ ArraySet<String> additionalChangedPackages =
+ mOverlayReferenceMapper.removePkg(setting.name);
+
mFeatureConfig.updatePackageState(setting, true /*removed*/);
// After removing all traces of the package, if it's part of a shared user, re-add other
@@ -1109,6 +1136,25 @@
siblingSetting, settings, users, settings.size());
}
}
+
+ if (mShouldFilterCache != null) {
+ if (additionalChangedPackages != null) {
+ for (int index = 0; index < additionalChangedPackages.size(); index++) {
+ String changedPackage = additionalChangedPackages.valueAt(index);
+ PackageSetting changedPkgSetting = settings.get(changedPackage);
+ if (changedPkgSetting == null) {
+ // It's possible for the overlay mapper to know that an actor
+ // package changed via an explicit reference, even if the actor
+ // isn't installed, so skip if that's the case.
+ continue;
+ }
+
+ updateShouldFilterCacheForPackage(mShouldFilterCache, null,
+ changedPkgSetting, settings, users, settings.size());
+ }
+ }
+ }
+
onChanged();
}
});
diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java
index 6875b8a..ec79483 100644
--- a/services/core/java/com/android/server/pm/DumpState.java
+++ b/services/core/java/com/android/server/pm/DumpState.java
@@ -45,6 +45,7 @@
public static final int DUMP_QUERIES = 1 << 26;
public static final int DUMP_KNOWN_PACKAGES = 1 << 27;
public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28;
+ public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1;
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 91a66ac..dd80e16 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -636,9 +636,25 @@
Objects.requireNonNull(component);
// All right, create the sender.
- Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
+ final int callingUid = injectBinderCallingUid();
final long identity = Binder.clearCallingIdentity();
try {
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ Intent packageIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT)
+ .setPackage(component.getPackageName());
+ List<ResolveInfo> apps = pmInt.queryIntentActivities(packageIntent,
+ packageIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ callingUid, user.getIdentifier());
+ // ensure that the component is present in the list
+ if (!apps.stream().anyMatch(
+ ri -> component.getClassName().equals(ri.activityInfo.name))) {
+ return null;
+ }
+
+ Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
final PendingIntent pi = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2849982..59039ba 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1914,6 +1914,18 @@
*/
private interface Computer {
+ /**
+ * Administrative statistics: record that the snapshot has been used. Every call
+ * to use() increments the usage counter.
+ */
+ void use();
+
+ /**
+ * Fetch the snapshot usage counter.
+ * @return The number of times this snapshot was used.
+ */
+ int getUsed();
+
@NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType,
int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid,
int userId, boolean resolveForStart, boolean allowDynamicSplits);
@@ -2065,6 +2077,9 @@
*/
private static class ComputerEngine implements Computer {
+ // The administrative use counter.
+ private int mUsed = 0;
+
// Cached attributes. The names in this class are the same as the
// names in PackageManagerService; see that class for documentation.
protected final Settings mSettings;
@@ -2157,6 +2172,20 @@
mService = args.service;
}
+ /**
+ * Record that the snapshot was used.
+ */
+ public void use() {
+ mUsed++;
+ }
+
+ /**
+ * Return the usage counter.
+ */
+ public int getUsed() {
+ return mUsed;
+ }
+
public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
int filterCallingUid, int userId, boolean resolveForStart,
@@ -4885,121 +4914,11 @@
*/
private final Object mSnapshotLock = new Object();
- // A counter of all queries that hit the current snapshot.
- @GuardedBy("mSnapshotLock")
- private int mSnapshotHits = 0;
-
- // A class to record snapshot statistics.
- private static class SnapshotStatistics {
- // A build time is "big" if it takes longer than 5ms.
- private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5);
-
- // A snapshot is in quick succession to the previous snapshot if it less than
- // 100ms since the previous snapshot.
- private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS =
- TimeUnit.MILLISECONDS.toNanos(100);
-
- // The interval between snapshot statistics logging, in ns.
- private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10);
-
- // The throttle parameters for big build reporting. Do not report more than this
- // many events in a single log interval.
- private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
-
- // The time the snapshot statistics were last logged.
- private long mStatisticsSent = 0;
-
- // The number of build events logged since the last periodic log.
- private int mLoggedBuilds = 0;
-
- // The time of the last build.
- private long mLastBuildTime = 0;
-
- // The number of times the snapshot has been rebuilt since the statistics were
- // last logged.
- private int mRebuilds = 0;
-
- // The number of times the snapshot has been used since it was rebuilt.
- private int mReused = 0;
-
- // The number of "big" build times since the last log. "Big" is defined by
- // SNAPSHOT_BIG_BUILD_TIME.
- private int mBigBuilds = 0;
-
- // The number of quick rebuilds. "Quick" is defined by
- // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS.
- private int mQuickRebuilds = 0;
-
- // The time take to build a snapshot. This is cumulative over the rebuilds recorded
- // in mRebuilds, so the average time to build a snapshot is given by
- // mBuildTimeNs/mRebuilds.
- private int mBuildTimeNs = 0;
-
- // The maximum build time since the last log.
- private long mMaxBuildTimeNs = 0;
-
- // The constant that converts ns to ms. This is the divisor.
- private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1);
-
- // Convert ns to an int ms. The maximum range of this method is about 24 days.
- // There is no expectation that an event will take longer than that.
- private int nsToMs(long ns) {
- return (int) (ns / NS_TO_MS);
- }
-
- // The single method records a rebuild. The "now" parameter is passed in because
- // the caller needed it to computer the duration, so pass it in to avoid
- // recomputing it.
- private void rebuild(long now, long done, int hits) {
- if (mStatisticsSent == 0) {
- mStatisticsSent = now;
- }
- final long elapsed = now - mLastBuildTime;
- final long duration = done - now;
- mLastBuildTime = now;
-
- if (mMaxBuildTimeNs < duration) {
- mMaxBuildTimeNs = duration;
- }
- mRebuilds++;
- mReused += hits;
- mBuildTimeNs += duration;
-
- boolean log_build = false;
- if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) {
- log_build = true;
- mBigBuilds++;
- }
- if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) {
- log_build = true;
- mQuickRebuilds++;
- }
- if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) {
- EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed));
- mLoggedBuilds++;
- }
-
- final long log_interval = now - mStatisticsSent;
- if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) {
- EventLogTags.writePmSnapshotStats(mRebuilds, mReused,
- mBigBuilds, mQuickRebuilds,
- nsToMs(mMaxBuildTimeNs),
- nsToMs(mBuildTimeNs));
- mStatisticsSent = now;
- mRebuilds = 0;
- mReused = 0;
- mBuildTimeNs = 0;
- mMaxBuildTimeNs = 0;
- mBigBuilds = 0;
- mQuickRebuilds = 0;
- mLoggedBuilds = 0;
- }
- }
- }
-
- // Snapshot statistics.
- @GuardedBy("mLock")
- private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics();
+ /**
+ * The snapshot statistics. These are collected to track performance and to identify
+ * situations in which the snapshots are misbehaving.
+ */
+ private final SnapshotStatistics mSnapshotStatistics;
// The snapshot disable/enable switch. An image with the flag set true uses snapshots
// and an image with the flag set false does not use snapshots.
@@ -5033,10 +4952,9 @@
Computer c = mSnapshotComputer;
if (sSnapshotCorked && (c != null)) {
// Snapshots are corked, which means new ones should not be built right now.
+ c.use();
return c;
}
- // Deliberately capture the value pre-increment
- final int hits = mSnapshotHits++;
if (sSnapshotInvalid || (c == null)) {
// The snapshot is invalid if it is marked as invalid or if it is null. If it
// is null, then it is currently being rebuilt by rebuildSnapshot().
@@ -5046,7 +4964,7 @@
// self-consistent (the lock is being held) and is current as of the time
// this function is entered.
if (sSnapshotInvalid) {
- rebuildSnapshot(hits);
+ rebuildSnapshot();
}
// Guaranteed to be non-null. mSnapshotComputer is only be set to null
@@ -5056,6 +4974,7 @@
c = mSnapshotComputer;
}
}
+ c.use();
return c;
}
}
@@ -5065,16 +4984,16 @@
* threads from using the invalid computer until it is rebuilt.
*/
@GuardedBy("mLock")
- private void rebuildSnapshot(int hits) {
- final long now = System.nanoTime();
+ private void rebuildSnapshot() {
+ final long now = SystemClock.currentTimeMicro();
+ final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed();
mSnapshotComputer = null;
sSnapshotInvalid = false;
final Snapshot args = new Snapshot(Snapshot.SNAPPED);
mSnapshotComputer = new ComputerEngine(args);
- final long done = System.nanoTime();
+ final long done = SystemClock.currentTimeMicro();
mSnapshotStatistics.rebuild(now, done, hits);
- mSnapshotHits = 0;
}
/**
@@ -6327,6 +6246,7 @@
mSnapshotEnabled = false;
mLiveComputer = createLiveComputer();
mSnapshotComputer = null;
+ mSnapshotStatistics = null;
mPackages.putAll(testParams.packages);
mEnableFreeCacheV2 = testParams.enableFreeCacheV2;
@@ -6479,17 +6399,20 @@
mDomainVerificationManager = injector.getDomainVerificationManagerInternal();
mDomainVerificationManager.setConnection(mDomainVerificationConnection);
- // Create the computer as soon as the state objects have been installed. The
- // cached computer is the same as the live computer until the end of the
- // constructor, at which time the invalidation method updates it. The cache is
- // corked initially to ensure a cached computer is not built until the end of the
- // constructor.
- mSnapshotEnabled = SNAPSHOT_ENABLED;
- sSnapshotCorked = true;
- sSnapshotInvalid = true;
- mLiveComputer = createLiveComputer();
- mSnapshotComputer = null;
- registerObserver();
+ synchronized (mLock) {
+ // Create the computer as soon as the state objects have been installed. The
+ // cached computer is the same as the live computer until the end of the
+ // constructor, at which time the invalidation method updates it. The cache is
+ // corked initially to ensure a cached computer is not built until the end of the
+ // constructor.
+ mSnapshotEnabled = SNAPSHOT_ENABLED;
+ sSnapshotCorked = true;
+ sSnapshotInvalid = true;
+ mSnapshotStatistics = new SnapshotStatistics();
+ mLiveComputer = createLiveComputer();
+ mSnapshotComputer = null;
+ registerObserver();
+ }
// CHECKSTYLE:OFF IndentationCheck
synchronized (mInstallLock) {
@@ -15315,9 +15238,8 @@
final BroadcastOptions bOptions = getTemporaryAppAllowlistBroadcastOptions(
REASON_LOCKED_BOOT_COMPLETED);
am.broadcastIntentWithFeature(null, null, lockedBcIntent, null, null, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
- false, false,
- userId);
+ requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+ bOptions.toBundle(), false, false, userId);
// Deliver BOOT_COMPLETED only if user is unlocked
final UserManagerInternal umInternal = mInjector.getUserManagerInternal();
@@ -15327,9 +15249,8 @@
bcIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
}
am.broadcastIntentWithFeature(null, null, bcIntent, null, null, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, bOptions.toBundle(),
- false, false,
- userId);
+ requiredPermissions, null, android.app.AppOpsManager.OP_NONE,
+ bOptions.toBundle(), false, false, userId);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -22197,7 +22118,7 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
am.broadcastIntentWithFeature(null, null, intent, null, null,
- 0, null, null, null, android.app.AppOpsManager.OP_NONE,
+ 0, null, null, null, null, android.app.AppOpsManager.OP_NONE,
null, false, false, userId);
} catch (RemoteException e) {
}
@@ -23959,6 +23880,7 @@
pw.println(" dexopt: dump dexopt state");
pw.println(" compiler-stats: dump compiler statistics");
pw.println(" service-permissions: dump permissions required by services");
+ pw.println(" snapshot: dump snapshot statistics");
pw.println(" known-packages: dump known packages");
pw.println(" <package.name>: info about given package");
return;
@@ -24107,6 +24029,8 @@
dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES);
} else if ("t".equals(cmd) || "timeouts".equals(cmd)) {
dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS);
+ } else if ("snapshot".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS);
} else if ("write".equals(cmd)) {
synchronized (mLock) {
writeSettingsLPrTEMP();
@@ -24435,6 +24359,22 @@
pw.println(")");
}
}
+
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)) {
+ pw.println("Snapshot statistics");
+ if (!mSnapshotEnabled) {
+ pw.println(" Snapshots disabled");
+ } else {
+ int hits = 0;
+ synchronized (mSnapshotLock) {
+ if (mSnapshotComputer != null) {
+ hits = mSnapshotComputer.getUsed();
+ }
+ }
+ final long now = SystemClock.currentTimeMicro();
+ mSnapshotStatistics.dump(pw, " ", now, hits, true);
+ }
+ }
}
/**
@@ -27810,8 +27750,8 @@
};
try {
am.broadcastIntentWithFeature(null, null, intent, null, null, 0, null, null,
- requiredPermissions, android.app.AppOpsManager.OP_NONE, null, false, false,
- UserHandle.USER_ALL);
+ requiredPermissions, null, android.app.AppOpsManager.OP_NONE, null, false,
+ false, UserHandle.USER_ALL);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
new file mode 100644
index 0000000..c425bad5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -0,0 +1,622 @@
+/*
+ * 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.annotation.Nullable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * This class records statistics about PackageManagerService snapshots. It maintains two sets of
+ * statistics: a periodic set which represents the last 10 minutes, and a cumulative set since
+ * process boot. The key metrics that are recorded are:
+ * <ul>
+ * <li> The time to create a snapshot - this is the performance cost of a snapshot
+ * <li> The lifetime of the snapshot - creation time over lifetime is the amortized cost
+ * <li> The number of times a snapshot is reused - this is the number of times lock
+ * contention was avoided.
+ * </ul>
+
+ * The time conversions in this class are designed to keep arithmetic using ints, rather
+ * than longs. Raw times are supplied as longs in units of us. These are left long.
+ * Rebuild durations however, are converted to ints. An int can express a duration of
+ * approximately 35 minutes. This is longer than any expected snapshot rebuild time, so
+ * an int is satisfactory. The exception is the cumulative rebuild time over the course
+ * of a monitoring cycle: this value is kept long since the cycle time is one week and in
+ * a badly behaved system, the rebuild time might exceed 35 minutes.
+
+ * @hide
+ */
+public class SnapshotStatistics {
+ /**
+ * The interval at which statistics should be ticked. It is 60s. The interval is in
+ * units of milliseconds because that is what's required by Handler.sendMessageDelayed().
+ */
+ public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000;
+
+ /**
+ * The number of ticks for long statistics. This is one week.
+ */
+ public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60;
+
+ /**
+ * The number snapshot event logs that can be generated in a single logging interval.
+ * A small number limits the logging generated by this class. A snapshot event log is
+ * generated for every big snapshot build time, up to the limit, or whenever the
+ * maximum build time is exceeded in the logging interval.
+ */
+ public static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10;
+
+ /**
+ * The number of microseconds in a millisecond.
+ */
+ private static final int US_IN_MS = 1000;
+
+ /**
+ * A snapshot build time is "big" if it takes longer than 10ms.
+ */
+ public static final int SNAPSHOT_BIG_BUILD_TIME_US = 10 * US_IN_MS;
+
+ /**
+ * A snapshot build time is reportable if it takes longer than 30ms. Testing shows
+ * that this is very rare.
+ */
+ public static final int SNAPSHOT_REPORTABLE_BUILD_TIME_US = 30 * US_IN_MS;
+
+ /**
+ * A snapshot is short-lived it used fewer than 5 times.
+ */
+ public static final int SNAPSHOT_SHORT_LIFETIME = 5;
+
+ /**
+ * The lock to control access to this object.
+ */
+ private final Object mLock = new Object();
+
+ /**
+ * The bins for the build time histogram. Values are in us.
+ */
+ private final BinMap mTimeBins;
+
+ /**
+ * The bins for the snapshot use histogram.
+ */
+ private final BinMap mUseBins;
+
+ /**
+ * The number of events reported in the current tick.
+ */
+ private int mEventsReported = 0;
+
+ /**
+ * The tick counter. At the default tick interval, this wraps every 4000 years or so.
+ */
+ private int mTicks = 0;
+
+ /**
+ * The handler used for the periodic ticks.
+ */
+ private Handler mHandler = null;
+
+ /**
+ * Convert ns to an int ms. The maximum range of this method is about 24 days. There
+ * is no expectation that an event will take longer than that.
+ */
+ private int usToMs(int us) {
+ return us / US_IN_MS;
+ }
+
+ /**
+ * This class exists to provide a fast bin lookup for histograms. An instance has an
+ * integer array that maps incoming values to bins. Values larger than the array are
+ * mapped to the top-most bin.
+ */
+ private static class BinMap {
+
+ // The number of bins
+ private int mCount;
+ // The mapping of low integers to bins
+ private int[] mBinMap;
+ // The maximum mapped value. Values at or above this are mapped to the
+ // top bin.
+ private int mMaxBin;
+ // A copy of the original key
+ private int[] mUserKey;
+
+ /**
+ * Create a bin map. The input is an array of integers, which must be
+ * monotonically increasing (this is not checked). The result is an integer array
+ * as long as the largest value in the input.
+ */
+ BinMap(int[] userKey) {
+ mUserKey = Arrays.copyOf(userKey, userKey.length);
+ // The number of bins is the length of the keys, plus 1 (for the max).
+ mCount = mUserKey.length + 1;
+ // The maximum value is one more than the last one in the map.
+ mMaxBin = mUserKey[mUserKey.length - 1] + 1;
+ mBinMap = new int[mMaxBin + 1];
+
+ int j = 0;
+ for (int i = 0; i < mUserKey.length; i++) {
+ while (j <= mUserKey[i]) {
+ mBinMap[j] = i;
+ j++;
+ }
+ }
+ mBinMap[mMaxBin] = mUserKey.length;
+ }
+
+ /**
+ * Map a value to a bin.
+ */
+ public int getBin(int x) {
+ if (x >= 0 && x < mMaxBin) {
+ return mBinMap[x];
+ } else if (x >= mMaxBin) {
+ return mBinMap[mMaxBin];
+ } else {
+ // x is negative. The bin will not be used.
+ return 0;
+ }
+ }
+
+ /**
+ * The number of bins in this map
+ */
+ public int count() {
+ return mCount;
+ }
+
+ /**
+ * For convenience, return the user key.
+ */
+ public int[] userKeys() {
+ return mUserKey;
+ }
+ }
+
+ /**
+ * A complete set of statistics. These are public, making it simpler for a client to
+ * fetch the individual fields.
+ */
+ public class Stats {
+
+ /**
+ * The start time for this set of statistics, in us.
+ */
+ public long mStartTimeUs = 0;
+
+ /**
+ * The completion time for this set of statistics, in ns. A value of zero means
+ * the statistics are still active.
+ */
+ public long mStopTimeUs = 0;
+
+ /**
+ * The build-time histogram. The total number of rebuilds is the sum over the
+ * histogram entries.
+ */
+ public int[] mTimes;
+
+ /**
+ * The reuse histogram. The total number of snapshot uses is the sum over the
+ * histogram entries.
+ */
+ public int[] mUsed;
+
+ /**
+ * The total number of rebuilds. This could be computed by summing over the use
+ * bins, but is maintained separately for convenience.
+ */
+ public int mTotalBuilds = 0;
+
+ /**
+ * The total number of times any snapshot was used.
+ */
+ public int mTotalUsed = 0;
+
+ /**
+ * The total number of builds that count as big, which means they took longer than
+ * SNAPSHOT_BIG_BUILD_TIME_NS.
+ */
+ public int mBigBuilds = 0;
+
+ /**
+ * The total number of short-lived snapshots
+ */
+ public int mShortLived = 0;
+
+ /**
+ * The time taken to build snapshots. This is cumulative over the rebuilds
+ * recorded in mRebuilds, so the average time to build a snapshot is given by
+ * mBuildTimeNs/mRebuilds. Note that this cannot be computed from the histogram.
+ */
+ public long mTotalTimeUs = 0;
+
+ /**
+ * The maximum build time since the last log.
+ */
+ public int mMaxBuildTimeUs = 0;
+
+ /**
+ * Record the rebuild. The parameters are the length of time it took to build the
+ * latest snapshot, and the number of times the _previous_ snapshot was used. A
+ * negative value for used signals an invalid value, which is the case the first
+ * time a snapshot is every built.
+ */
+ private void rebuild(int duration, int used,
+ int buildBin, int useBin, boolean big, boolean quick) {
+ mTotalBuilds++;
+ mTimes[buildBin]++;
+
+ if (used >= 0) {
+ mTotalUsed += used;
+ mUsed[useBin]++;
+ }
+
+ mTotalTimeUs += duration;
+ boolean reportIt = false;
+
+ if (big) {
+ mBigBuilds++;
+ }
+ if (quick) {
+ mShortLived++;
+ }
+ if (mMaxBuildTimeUs < duration) {
+ mMaxBuildTimeUs = duration;
+ }
+ }
+
+ private Stats(long now) {
+ mStartTimeUs = now;
+ mTimes = new int[mTimeBins.count()];
+ mUsed = new int[mUseBins.count()];
+ }
+
+ /**
+ * Create a copy of the argument. The copy is made under lock but can then be
+ * used without holding the lock.
+ */
+ private Stats(Stats orig) {
+ mStartTimeUs = orig.mStartTimeUs;
+ mStopTimeUs = orig.mStopTimeUs;
+ mTimes = Arrays.copyOf(orig.mTimes, orig.mTimes.length);
+ mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length);
+ mTotalBuilds = orig.mTotalBuilds;
+ mTotalUsed = orig.mTotalUsed;
+ mBigBuilds = orig.mBigBuilds;
+ mShortLived = orig.mShortLived;
+ mTotalTimeUs = orig.mTotalTimeUs;
+ mMaxBuildTimeUs = orig.mMaxBuildTimeUs;
+ }
+
+ /**
+ * Set the end time for the statistics. The end time is used only for reporting
+ * in the dump() method.
+ */
+ private void complete(long stop) {
+ mStopTimeUs = stop;
+ }
+
+ /**
+ * Format a time span into ddd:HH:MM:SS. The input is in us.
+ */
+ private String durationToString(long us) {
+ // s has a range of several years
+ int s = (int) (us / (1000 * 1000));
+ int m = s / 60;
+ s %= 60;
+ int h = m / 60;
+ m %= 60;
+ int d = h / 24;
+ h %= 24;
+ if (d != 0) {
+ return TextUtils.formatSimple("%2d:%02d:%02d:%02d", d, h, m, s);
+ } else if (h != 0) {
+ return TextUtils.formatSimple("%2s %02d:%02d:%02d", "", h, m, s);
+ } else {
+ return TextUtils.formatSimple("%2s %2s %2d:%02d", "", "", m, s);
+ }
+ }
+
+ /**
+ * Print the prefix for dumping. This does not generate a line to the output.
+ */
+ private void dumpPrefix(PrintWriter pw, String indent, long now, boolean header,
+ String title) {
+ pw.print(indent + " ");
+ if (header) {
+ pw.format(Locale.US, "%-23s", title);
+ } else {
+ pw.format(Locale.US, "%11s", durationToString(now - mStartTimeUs));
+ if (mStopTimeUs != 0) {
+ pw.format(Locale.US, " %11s", durationToString(now - mStopTimeUs));
+ } else {
+ pw.format(Locale.US, " %11s", "now");
+ }
+ }
+ }
+
+ /**
+ * Dump the summary statistics record. Choose the header or the data.
+ * number of builds
+ * number of uses
+ * number of big builds
+ * number of short lifetimes
+ * cumulative build time, in seconds
+ * maximum build time, in ms
+ */
+ private void dumpStats(PrintWriter pw, String indent, long now, boolean header) {
+ dumpPrefix(pw, indent, now, header, "Summary stats");
+ if (header) {
+ pw.format(Locale.US, " %10s %10s %10s %10s %10s %10s",
+ "TotBlds", "TotUsed", "BigBlds", "ShortLvd",
+ "TotTime", "MaxTime");
+ } else {
+ pw.format(Locale.US,
+ " %10d %10d %10d %10d %10d %10d",
+ mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+ mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000);
+ }
+ pw.println();
+ }
+
+ /**
+ * Dump the build time histogram. Choose the header or the data.
+ */
+ private void dumpTimes(PrintWriter pw, String indent, long now, boolean header) {
+ dumpPrefix(pw, indent, now, header, "Build times");
+ if (header) {
+ int[] keys = mTimeBins.userKeys();
+ for (int i = 0; i < keys.length; i++) {
+ pw.format(Locale.US, " %10s",
+ TextUtils.formatSimple("<= %dms", keys[i]));
+ }
+ pw.format(Locale.US, " %10s",
+ TextUtils.formatSimple("> %dms", keys[keys.length - 1]));
+ } else {
+ for (int i = 0; i < mTimes.length; i++) {
+ pw.format(Locale.US, " %10d", mTimes[i]);
+ }
+ }
+ pw.println();
+ }
+
+ /**
+ * Dump the usage histogram. Choose the header or the data.
+ */
+ private void dumpUsage(PrintWriter pw, String indent, long now, boolean header) {
+ dumpPrefix(pw, indent, now, header, "Use counters");
+ if (header) {
+ int[] keys = mUseBins.userKeys();
+ for (int i = 0; i < keys.length; i++) {
+ pw.format(Locale.US, " %10s", TextUtils.formatSimple("<= %d", keys[i]));
+ }
+ pw.format(Locale.US, " %10s",
+ TextUtils.formatSimple("> %d", keys[keys.length - 1]));
+ } else {
+ for (int i = 0; i < mUsed.length; i++) {
+ pw.format(Locale.US, " %10d", mUsed[i]);
+ }
+ }
+ pw.println();
+ }
+
+ /**
+ * Dump something, based on the "what" parameter.
+ */
+ private void dump(PrintWriter pw, String indent, long now, boolean header, String what) {
+ if (what.equals("stats")) {
+ dumpStats(pw, indent, now, header);
+ } else if (what.equals("times")) {
+ dumpTimes(pw, indent, now, header);
+ } else if (what.equals("usage")) {
+ dumpUsage(pw, indent, now, header);
+ } else {
+ throw new IllegalArgumentException("unrecognized choice: " + what);
+ }
+ }
+
+ /**
+ * Report the object via an event. Presumably the record indicates an anomalous
+ * incident.
+ */
+ private void report() {
+ EventLogTags.writePmSnapshotStats(
+ mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
+ mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS);
+ }
+ }
+
+ /**
+ * Long statistics. These roll over approximately every week.
+ */
+ private Stats[] mLong;
+
+ /**
+ * Short statistics. These roll over approximately every minute;
+ */
+ private Stats[] mShort;
+
+ /**
+ * The time of the last build. This can be used to compute the length of time a
+ * snapshot existed before being replaced.
+ */
+ private long mLastBuildTime = 0;
+
+ /**
+ * Create a snapshot object. Initialize the bin levels. The last bin catches
+ * everything that is not caught earlier, so its value is not really important.
+ */
+ public SnapshotStatistics() {
+ // Create the bin thresholds. The time bins are in units of us.
+ mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+ mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+
+ // Create the raw statistics
+ final long now = SystemClock.currentTimeMicro();
+ mLong = new Stats[2];
+ mLong[0] = new Stats(now);
+ mShort = new Stats[10];
+ mShort[0] = new Stats(now);
+
+ // Create the message handler for ticks and start the ticker.
+ mHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ SnapshotStatistics.this.handleMessage(msg);
+ }
+ };
+ scheduleTick();
+ }
+
+ /**
+ * Handle a message. The only messages are ticks, so the message parameter is ignored.
+ */
+ private void handleMessage(@Nullable Message msg) {
+ tick();
+ scheduleTick();
+ }
+
+ /**
+ * Schedule one tick, a tick interval in the future.
+ */
+ private void scheduleTick() {
+ mHandler.sendEmptyMessageDelayed(0, SNAPSHOT_TICK_INTERVAL_MS);
+ }
+
+ /**
+ * Record a rebuild. Cumulative and current statistics are updated. Events may be
+ * generated.
+ * @param now The time at which the snapshot rebuild began, in ns.
+ * @param done The time at which the snapshot rebuild completed, in ns.
+ * @param hits The number of times the previous snapshot was used.
+ */
+ public void rebuild(long now, long done, int hits) {
+ // The duration has a span of about 2000s
+ final int duration = (int) (done - now);
+ boolean reportEvent = false;
+ synchronized (mLock) {
+ mLastBuildTime = now;
+
+ final int timeBin = mTimeBins.getBin(duration / 1000);
+ final int useBin = mUseBins.getBin(hits);
+ final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US;
+ final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME;
+
+ mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+ mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick);
+ if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) {
+ if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) {
+ reportEvent = true;
+ }
+ }
+ }
+ // The IO to the logger is done outside the lock.
+ if (reportEvent) {
+ // Report the first N big builds, and every new maximum after that.
+ EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits);
+ }
+ }
+
+ /**
+ * Roll a stats array. Shift the elements up an index and create a new element at
+ * index zero. The old element zero is completed with the specified time.
+ */
+ private void shift(Stats[] s, long now) {
+ s[0].complete(now);
+ for (int i = s.length - 1; i > 0; i--) {
+ s[i] = s[i - 1];
+ }
+ s[0] = new Stats(now);
+ }
+
+ /**
+ * Roll the statistics.
+ * <ul>
+ * <li> Roll the quick statistics immediately.
+ * <li> Roll the long statistics every SNAPSHOT_LONG_TICKER ticks. The long
+ * statistics hold a week's worth of data.
+ * <li> Roll the logging statistics every SNAPSHOT_LOGGING_TICKER ticks. The logging
+ * statistics hold 10 minutes worth of data.
+ * </ul>
+ */
+ private void tick() {
+ synchronized (mLock) {
+ long now = SystemClock.currentTimeMicro();
+ mTicks++;
+ if (mTicks % SNAPSHOT_LONG_TICKS == 0) {
+ shift(mLong, now);
+ }
+ shift(mShort, now);
+ mEventsReported = 0;
+ }
+ }
+
+ /**
+ * Dump the statistics. The header is dumped from l[0], so that must not be null.
+ */
+ private void dump(PrintWriter pw, String indent, long now, Stats[] l, Stats[] s, String what) {
+ l[0].dump(pw, indent, now, true, what);
+ for (int i = 0; i < s.length; i++) {
+ if (s[i] != null) {
+ s[i].dump(pw, indent, now, false, what);
+ }
+ }
+ for (int i = 0; i < l.length; i++) {
+ if (l[i] != null) {
+ l[i].dump(pw, indent, now, false, what);
+ }
+ }
+ }
+
+ /**
+ * Dump the statistics. The format is compatible with the PackageManager dumpsys
+ * output.
+ */
+ public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) {
+ // Grab the raw statistics under lock, but print them outside of the lock.
+ Stats[] l;
+ Stats[] s;
+ synchronized (mLock) {
+ l = Arrays.copyOf(mLong, mLong.length);
+ l[0] = new Stats(l[0]);
+ s = Arrays.copyOf(mShort, mShort.length);
+ s[0] = new Stats(s[0]);
+ }
+ pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded);
+ pw.println();
+ dump(pw, indent, now, l, s, "stats");
+ if (!full) {
+ return;
+ }
+ pw.println();
+ dump(pw, indent, now, l, s, "times");
+ pw.println();
+ dump(pw, indent, now, l, s, "usage");
+ }
+}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e913829..81cfbf7 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -85,6 +85,15 @@
"include-annotation": "android.platform.test.annotations.Presubmit"
}
]
+ },
+ {
+ "name": "PackageManagerServiceHostTests",
+ "file_patterns": ["AppsFilter\\.java"],
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+ }
+ ]
}
],
"postsubmit": [
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
index bf2b3c7..a8a6a72 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java
@@ -24,6 +24,7 @@
import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.parsing.component.ParsedIntentInfo;
import android.os.Build;
+import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Patterns;
@@ -32,7 +33,6 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.util.List;
-import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -251,6 +251,10 @@
* improve the reliability of any legacy verifiers.
*/
private boolean isValidHost(String host) {
+ if (TextUtils.isEmpty(host)) {
+ return false;
+ }
+
mDomainMatcher.reset(host);
return mDomainMatcher.matches();
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
index 2a17c6d..3f00a9d 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java
@@ -29,6 +29,7 @@
import android.os.ServiceSpecificException;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
public class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
@@ -110,6 +111,7 @@
public List<DomainOwner> getOwnersForDomain(@NonNull String domain,
@UserIdInt int userId) {
try {
+ Objects.requireNonNull(domain);
return mService.getOwnersForDomain(domain, userId);
} catch (Exception e) {
throw rethrow(e);
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 4ae79a2..f0fdad0 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
@@ -742,6 +742,7 @@
}
public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
+ Objects.requireNonNull(domain);
mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
userId);
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 44ff3eb..246810f 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -22,6 +22,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
+import android.util.Patterns;
import com.android.internal.util.CollectionUtils;
import com.android.server.compat.PlatformCompat;
@@ -29,9 +31,13 @@
import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.util.Set;
+import java.util.regex.Matcher;
public final class DomainVerificationUtils {
+ private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
+ () -> Patterns.DOMAIN_NAME.matcher(""));
+
/**
* Consolidates package exception messages. A generic unavailable message is included since the
* caller doesn't bother to check why the package isn't available.
@@ -48,6 +54,15 @@
return false;
}
+ String host = intent.getData().getHost();
+ if (TextUtils.isEmpty(host)) {
+ return false;
+ }
+
+ if (!sCachedMatcher.get().reset(host).matches()) {
+ return false;
+ }
+
Set<String> categories = intent.getCategories();
int categoriesSize = CollectionUtils.size(categories);
if (categoriesSize > 2) {
diff --git a/services/core/java/com/android/server/power/FaceDownDetector.java b/services/core/java/com/android/server/power/FaceDownDetector.java
index 474ce59..816c81d 100644
--- a/services/core/java/com/android/server/power/FaceDownDetector.java
+++ b/services/core/java/com/android/server/power/FaceDownDetector.java
@@ -330,7 +330,9 @@
private boolean isEnabled() {
return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE, KEY_FEATURE_ENABLED,
- DEFAULT_FEATURE_ENABLED);
+ DEFAULT_FEATURE_ENABLED)
+ && mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_flipToScreenOffEnabled);
}
private float getAccelerationThreshold() {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 4adcfb6..9e19f57 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -613,9 +613,11 @@
PackageInstaller.SessionInfo session = mContext.getPackageManager()
.getPackageInstaller().getSessionInfo(rollback.getStagedSessionId());
if (session == null || session.isStagedSessionFailed()) {
- iter.remove();
- deleteRollback(rollback,
- "Session " + rollback.getStagedSessionId() + " not existed or failed");
+ if (rollback.isEnabling()) {
+ iter.remove();
+ deleteRollback(rollback, "Session " + rollback.getStagedSessionId()
+ + " not existed or failed");
+ }
continue;
}
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4f5e8fa..072cc16f 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -16,26 +16,22 @@
package com.android.server.timedetector;
-import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
-import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
-import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
-
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.os.Build;
+import android.database.ContentObserver;
+import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.util.Slog;
-import com.android.internal.R;
-import com.android.server.timedetector.TimeDetectorStrategy.Origin;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
import java.time.Instant;
import java.util.Objects;
@@ -43,62 +39,71 @@
/**
* The real implementation of {@link TimeDetectorStrategyImpl.Environment} used on device.
*/
-public final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
+final class EnvironmentImpl implements TimeDetectorStrategyImpl.Environment {
- private static final String TAG = TimeDetectorService.TAG;
-
- private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
-
- /**
- * Time in the past. If automatic time suggestion is before this point, it's
- * incorrect for sure.
- */
- private static final Instant TIME_LOWER_BOUND = Instant.ofEpochMilli(
- Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
-
- /**
- * By default telephony and network only suggestions are accepted and telephony takes
- * precedence over network.
- */
- private static final @Origin int[] DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES =
- { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
-
- /**
- * If a newly calculated system clock time and the current system clock time differs by this or
- * more the system clock will actually be updated. Used to prevent the system clock being set
- * for only minor differences.
- */
- private final int mSystemClockUpdateThresholdMillis;
+ private static final String LOG_TAG = TimeDetectorService.TAG;
@NonNull private final Context mContext;
+ @NonNull private final Handler mHandler;
+ @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
@NonNull private final ContentResolver mContentResolver;
@NonNull private final PowerManager.WakeLock mWakeLock;
@NonNull private final AlarmManager mAlarmManager;
@NonNull private final UserManager mUserManager;
- @NonNull private final int[] mOriginPriorities;
- public EnvironmentImpl(@NonNull Context context) {
+ // @NonNull after setConfigChangeListener() is called.
+ @GuardedBy("this")
+ private ConfigurationChangeListener mConfigChangeListener;
+
+ EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
+ @NonNull ServiceConfigAccessor serviceConfigAccessor) {
mContext = Objects.requireNonNull(context);
mContentResolver = Objects.requireNonNull(context.getContentResolver());
+ mHandler = Objects.requireNonNull(handler);
+ mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
PowerManager powerManager = context.getSystemService(PowerManager.class);
mWakeLock = Objects.requireNonNull(
- powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG));
+ powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG));
mAlarmManager = Objects.requireNonNull(context.getSystemService(AlarmManager.class));
mUserManager = Objects.requireNonNull(context.getSystemService(UserManager.class));
- mSystemClockUpdateThresholdMillis =
- SystemProperties.getInt("ro.sys.time_detector_update_diff",
- SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+ // Wire up the config change listeners. All invocations are performed on the mHandler
+ // thread.
- mOriginPriorities = getOriginPriorities(context);
+ ContentResolver contentResolver = context.getContentResolver();
+ contentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
+ new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ handleAutoTimeDetectionChangedOnHandlerThread();
+ }
+ });
+ }
+
+ /** Internal method for handling the auto time setting being changed. */
+ private void handleAutoTimeDetectionChangedOnHandlerThread() {
+ synchronized (this) {
+ if (mConfigChangeListener == null) {
+ Slog.wtf(LOG_TAG, "mConfigChangeListener is unexpectedly null");
+ }
+ mConfigChangeListener.onChange();
+ }
+ }
+
+ @Override
+ public void setConfigChangeListener(@NonNull ConfigurationChangeListener listener) {
+ synchronized (this) {
+ mConfigChangeListener = Objects.requireNonNull(listener);
+ }
}
@Override
public int systemClockUpdateThresholdMillis() {
- return mSystemClockUpdateThresholdMillis;
+ return mServiceConfigAccessor.systemClockUpdateThresholdMillis();
}
@Override
@@ -112,12 +117,12 @@
@Override
public Instant autoTimeLowerBound() {
- return TIME_LOWER_BOUND;
+ return mServiceConfigAccessor.autoTimeLowerBound();
}
@Override
public int[] autoOriginPriorities() {
- return mOriginPriorities;
+ return mServiceConfigAccessor.getOriginPriorities();
}
@Override
@@ -131,7 +136,7 @@
@Override
public void acquireWakeLock() {
if (mWakeLock.isHeld()) {
- Slog.wtf(TAG, "WakeLock " + mWakeLock + " already held");
+ Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " already held");
}
mWakeLock.acquire();
}
@@ -160,7 +165,7 @@
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
- Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
+ Slog.wtf(LOG_TAG, "WakeLock " + mWakeLock + " not held");
}
}
@@ -168,20 +173,4 @@
UserHandle userHandle = UserHandle.of(userId);
return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME, userHandle);
}
-
- private static int[] getOriginPriorities(@NonNull Context context) {
- String[] originStrings =
- context.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
- if (originStrings.length == 0) {
- return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
- } else {
- int[] origins = new int[originStrings.length];
- for (int i = 0; i < originStrings.length; i++) {
- int origin = stringToOrigin(originStrings[i]);
- origins[i] = origin;
- }
-
- return origins;
- }
- }
}
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
new file mode 100644
index 0000000..be4432a
--- /dev/null
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -0,0 +1,141 @@
+/*
+ * 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 com.android.server.timedetector;
+
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_NETWORK;
+import static com.android.server.timedetector.TimeDetectorStrategy.ORIGIN_TELEPHONY;
+import static com.android.server.timedetector.TimeDetectorStrategy.stringToOrigin;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.ArraySet;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A singleton that provides access to service configuration for time detection. This hides how
+ * configuration is split between static, compile-time config and dynamic, server-pushed flags. It
+ * provides a rudimentary mechanism to signal when values have changed.
+ */
+final class ServiceConfigAccessor {
+
+ private static final int SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT = 2 * 1000;
+
+ /**
+ * By default telephony and network only suggestions are accepted and telephony takes
+ * precedence over network.
+ */
+ private static final @TimeDetectorStrategy.Origin int[]
+ DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES = { ORIGIN_TELEPHONY, ORIGIN_NETWORK };
+
+ /**
+ * Time in the past. If an automatic time suggestion is before this point, it is sure to be
+ * incorrect.
+ */
+ private static final Instant TIME_LOWER_BOUND_DEFAULT = Instant.ofEpochMilli(
+ Long.max(android.os.Environment.getRootDirectory().lastModified(), Build.TIME));
+
+ private static final Set<String> SERVER_FLAGS_KEYS_TO_WATCH = Collections.unmodifiableSet(
+ new ArraySet<>(new String[] {
+ }));
+
+ private static final Object SLOCK = new Object();
+
+ /** The singleton instance. Initialized once in {@link #getInstance(Context)}. */
+ @GuardedBy("SLOCK")
+ @Nullable
+ private static ServiceConfigAccessor sInstance;
+
+ @NonNull private final Context mContext;
+ @NonNull private final ServerFlags mServerFlags;
+ @NonNull private final int[] mOriginPriorities;
+
+ /**
+ * If a newly calculated system clock time and the current system clock time differs by this or
+ * more the system clock will actually be updated. Used to prevent the system clock being set
+ * for only minor differences.
+ */
+ private final int mSystemClockUpdateThresholdMillis;
+
+ private ServiceConfigAccessor(@NonNull Context context) {
+ mContext = Objects.requireNonNull(context);
+ mServerFlags = ServerFlags.getInstance(mContext);
+ mOriginPriorities = getOriginPrioritiesInternal();
+ mSystemClockUpdateThresholdMillis =
+ SystemProperties.getInt("ro.sys.time_detector_update_diff",
+ SYSTEM_CLOCK_UPDATE_THRESHOLD_MILLIS_DEFAULT);
+ }
+
+ /** Returns the singleton instance. */
+ static ServiceConfigAccessor getInstance(Context context) {
+ synchronized (SLOCK) {
+ if (sInstance == null) {
+ sInstance = new ServiceConfigAccessor(context);
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Adds a listener that will be called when server flags related to this class change. The
+ * callbacks are delivered on the main looper thread.
+ *
+ * <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
+ * method.
+ */
+ void addListener(@NonNull ConfigurationChangeListener listener) {
+ mServerFlags.addListener(listener, SERVER_FLAGS_KEYS_TO_WATCH);
+ }
+
+ @NonNull
+ int[] getOriginPriorities() {
+ return mOriginPriorities;
+ }
+
+ int systemClockUpdateThresholdMillis() {
+ return mSystemClockUpdateThresholdMillis;
+ }
+
+ Instant autoTimeLowerBound() {
+ return TIME_LOWER_BOUND_DEFAULT;
+ }
+
+ private int[] getOriginPrioritiesInternal() {
+ String[] originStrings =
+ mContext.getResources().getStringArray(R.array.config_autoTimeSourcesPriority);
+ if (originStrings.length == 0) {
+ return DEFAULT_AUTOMATIC_TIME_ORIGIN_PRIORITIES;
+ } else {
+ int[] origins = new int[originStrings.length];
+ for (int i = 0; i < originStrings.length; i++) {
+ int origin = stringToOrigin(originStrings[i]);
+ origins[i] = origin;
+ }
+
+ return origins;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 27e2ee5..14cab38 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -27,12 +27,9 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
-import android.content.ContentResolver;
import android.content.Context;
-import android.database.ContentObserver;
import android.os.Binder;
import android.os.Handler;
-import android.provider.Settings;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.VisibleForTesting;
@@ -64,7 +61,16 @@
@Override
public void onStart() {
- TimeDetectorService service = TimeDetectorService.create(getContext());
+ Context context = getContext();
+ Handler handler = FgThread.getHandler();
+
+ ServiceConfigAccessor serviceConfigAccessor =
+ ServiceConfigAccessor.getInstance(context);
+ TimeDetectorStrategy timeDetectorStrategy =
+ TimeDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+
+ TimeDetectorService service =
+ new TimeDetectorService(context, handler, timeDetectorStrategy);
// Publish the binder service so it can be accessed from other (appropriately
// permissioned) processes.
@@ -77,28 +83,6 @@
@NonNull private final TimeDetectorStrategy mTimeDetectorStrategy;
@NonNull private final CallerIdentityInjector mCallerIdentityInjector;
- private static TimeDetectorService create(@NonNull Context context) {
- TimeDetectorStrategyImpl.Environment environment = new EnvironmentImpl(context);
- TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(environment);
-
- Handler handler = FgThread.getHandler();
- TimeDetectorService timeDetectorService =
- new TimeDetectorService(context, handler, timeDetectorStrategy);
-
- // Wire up event listening.
- ContentResolver contentResolver = context.getContentResolver();
- contentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
- new ContentObserver(handler) {
- @Override
- public void onChange(boolean selfChange) {
- timeDetectorService.handleAutoTimeDetectionChanged();
- }
- });
-
- return timeDetectorService;
- }
-
@VisibleForTesting
public TimeDetectorService(@NonNull Context context, @NonNull Handler handler,
@NonNull TimeDetectorStrategy timeDetectorStrategy) {
@@ -136,6 +120,7 @@
@Override
public boolean updateConfiguration(TimeConfiguration timeConfiguration) {
+ enforceManageTimeDetectorPermission();
// TODO(b/172891783) Add actual logic
return false;
}
@@ -185,12 +170,6 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestExternalTime(timeSignal));
}
- /** Internal method for handling the auto time setting being changed. */
- @VisibleForTesting
- public void handleAutoTimeDetectionChanged() {
- mHandler.post(mTimeDetectorStrategy::handleAutoTimeConfigChanged);
- }
-
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
@Nullable String[] args) {
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index cde66be..be382f0 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -92,12 +92,6 @@
/** Returns the configuration that controls time detector behaviour for specified user. */
ConfigurationInternal getConfigurationInternal(@UserIdInt int userId);
- /**
- * Handles the auto-time configuration changing For example, when the auto-time setting is
- * toggled on or off.
- */
- void handleAutoTimeConfigChanged();
-
// Utility methods below are to be moved to a better home when one becomes more obvious.
/**
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 289d8d6..db8a59e 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -29,6 +29,8 @@
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.TelephonyTimeSuggestion;
+import android.content.Context;
+import android.os.Handler;
import android.os.TimestampedValue;
import android.util.IndentingPrintWriter;
import android.util.LocalLog;
@@ -37,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.timezonedetector.ArrayMapWithHistory;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
import com.android.server.timezonedetector.ReferenceWithHistory;
import java.time.Instant;
@@ -132,6 +135,12 @@
public interface Environment {
/**
+ * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
+ * changes that could affect time detection. This is invoked during system server setup.
+ */
+ void setConfigChangeListener(@NonNull ConfigurationChangeListener listener);
+
+ /**
* The absolute threshold below which the system clock need not be updated. i.e. if setting
* the system clock would adjust it by less than this (either backwards or forwards) then it
* need not be set.
@@ -178,8 +187,19 @@
void releaseWakeLock();
}
+ static TimeDetectorStrategy create(
+ @NonNull Context context, @NonNull Handler handler,
+ @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+
+ TimeDetectorStrategyImpl.Environment environment =
+ new EnvironmentImpl(context, handler, serviceConfigAccessor);
+ return new TimeDetectorStrategyImpl(environment);
+ }
+
+ @VisibleForTesting
TimeDetectorStrategyImpl(@NonNull Environment environment) {
mEnvironment = Objects.requireNonNull(environment);
+ mEnvironment.setConfigChangeListener(this::handleAutoTimeConfigChanged);
}
@Override
@@ -279,8 +299,7 @@
return mEnvironment.configurationInternal(userId);
}
- @Override
- public synchronized void handleAutoTimeConfigChanged() {
+ private synchronized void handleAutoTimeConfigChanged() {
boolean enabled = mEnvironment.isAutoTimeDetectionEnabled();
// When automatic time detection is enabled we update the system clock instantly if we can.
// Conversely, when automatic time detection is disabled we leave the clock as it is.
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 0e5f3bf..b84f8a8 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -47,7 +47,7 @@
/**
* The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
*/
-public final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
+final class EnvironmentImpl implements TimeZoneDetectorStrategyImpl.Environment {
private static final String LOG_TAG = TimeZoneDetectorService.TAG;
private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 50d37f4..dddb11b 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -133,8 +133,8 @@
}
/**
- * Adds a listener that will be called server flags related to this class change. The callbacks
- * are delivered on the main looper thread.
+ * Adds a listener that will be called when server flags related to this class change. The
+ * callbacks are delivered on the main looper thread.
*
* <p>Note: Only for use by long-lived objects. There is deliberately no associated remove
* method.
diff --git a/services/core/java/com/android/server/trust/OWNERS b/services/core/java/com/android/server/trust/OWNERS
index b039c4b..e2c6ce1 100644
--- a/services/core/java/com/android/server/trust/OWNERS
+++ b/services/core/java/com/android/server/trust/OWNERS
@@ -1 +1 @@
-include /core/java/android/app/trust/OWNERS
+include /core/java/android/service/trust/OWNERS
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index a59b368..48ccad33 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -119,18 +119,18 @@
if (!mIsQuitting) {
mRouteSelectionCallback = new RouteSelectionCallback();
mConnectivityManager.requestBackgroundNetwork(
- getRouteSelectionRequest(), mHandler, mRouteSelectionCallback);
+ getRouteSelectionRequest(), mRouteSelectionCallback, mHandler);
mWifiBringupCallback = new NetworkBringupCallback();
mConnectivityManager.requestBackgroundNetwork(
- getWifiNetworkRequest(), mHandler, mWifiBringupCallback);
+ getWifiNetworkRequest(), mWifiBringupCallback, mHandler);
for (final int subId : mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup)) {
final NetworkBringupCallback cb = new NetworkBringupCallback();
mCellBringupCallbacks.add(cb);
mConnectivityManager.requestBackgroundNetwork(
- getCellNetworkRequestForSubId(subId), mHandler, cb);
+ getCellNetworkRequestForSubId(subId), cb, mHandler);
}
} else {
mRouteSelectionCallback = null;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e4dc8c2..f021072 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -96,6 +96,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
+import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.apphibernation.AppHibernationService;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -163,6 +165,9 @@
*/
private final LaunchObserverRegistryImpl mLaunchObserver;
@VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512;
+ private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>();
+ private AppHibernationManagerInternal mAppHibernationManagerInternal;
+ private boolean mIsAppHibernationEnabled;
/**
* The information created when an intent is incoming but we do not yet know whether it will be
@@ -789,6 +794,27 @@
}
}
+ @Nullable
+ private AppHibernationManagerInternal getAppHibernationManagerInternal() {
+ if (mAppHibernationManagerInternal == null) {
+ mIsAppHibernationEnabled = AppHibernationService.isAppHibernationEnabled();
+ mAppHibernationManagerInternal =
+ LocalServices.getService(AppHibernationManagerInternal.class);
+ }
+ return mAppHibernationManagerInternal;
+ }
+
+ /**
+ * Notifies the tracker before the package is unstopped because of launching activity.
+ * @param packageName The package to be unstopped.
+ */
+ void notifyBeforePackageUnstopped(@NonNull String packageName) {
+ final AppHibernationManagerInternal ahmInternal = getAppHibernationManagerInternal();
+ if (ahmInternal != null && mIsAppHibernationEnabled) {
+ mLastHibernationStates.put(packageName, ahmInternal.isHibernatingGlobally(packageName));
+ }
+ }
+
/**
* Notifies the tracker that we called immediately before we call bindApplication on the client.
*
@@ -823,6 +849,8 @@
}
stopLaunchTrace(info);
+ final Boolean isHibernating =
+ mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName);
if (abort) {
mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity);
launchObserverNotifyActivityLaunchCancelled(info);
@@ -830,7 +858,7 @@
if (info.isInterestingToLoggerAndObserver()) {
launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
- logAppTransitionFinished(info);
+ logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
}
info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
@@ -859,7 +887,7 @@
}
}
- private void logAppTransitionFinished(@NonNull TransitionInfo info) {
+ private void logAppTransitionFinished(@NonNull TransitionInfo info, boolean isHibernating) {
if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info);
// Take a snapshot of the transition info before sending it to the handler for logging.
@@ -868,7 +896,7 @@
if (info.isInterestingToLoggerAndObserver()) {
BackgroundThread.getHandler().post(() -> logAppTransition(
info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs,
- infoSnapshot));
+ infoSnapshot, isHibernating));
}
BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot));
if (info.mPendingFullyDrawn != null) {
@@ -880,7 +908,7 @@
// This gets called on a background thread without holding the activity manager lock.
private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs,
- TransitionInfoSnapshot info) {
+ TransitionInfoSnapshot info, boolean isHibernating) {
final LogMaker builder = new LogMaker(APP_TRANSITION);
builder.setPackageName(info.packageName);
builder.setType(info.type);
@@ -933,7 +961,8 @@
packageOptimizationInfo.getCompilationReason(),
packageOptimizationInfo.getCompilationFilter(),
info.sourceType,
- info.sourceEventDelayMs);
+ info.sourceEventDelayMs,
+ isHibernating);
if (DEBUG_METRICS) {
Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c8b7038..6d72999 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -587,7 +587,14 @@
AnimatingActivityRegistry mAnimatingActivityRegistry;
- private Task mLastParent;
+ // Set whenever the ActivityRecord gets reparented to a Task so we can know the last known
+ // parent was when the ActivityRecord is detached from the hierarchy
+ private Task mLastKnownParent;
+
+ // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task
+ // due to picture-in-picture. This gets cleared whenever this activity or the Task
+ // it references to gets removed. This should also be cleared when we move out of pip.
+ private Task mLastParentBeforePip;
boolean firstWindowDrawn;
/** Whether the visible window(s) of this activity is drawn. */
@@ -1096,6 +1103,9 @@
pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges));
}
}
+ if (mLastParentBeforePip != null) {
+ pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId);
+ }
dumpLetterboxInfo(pw, prefix);
}
@@ -1133,6 +1143,8 @@
pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius="
+ getLetterboxWallpaperBlurRadius());
}
+ pw.println(prefix + " letterboxHorizontalPositionMultiplier="
+ + mWmService.getLetterboxHorizontalPositionMultiplier());
}
/**
@@ -1349,7 +1361,7 @@
if (getDisplayContent() != null) {
getDisplayContent().mClosingApps.remove(this);
}
- } else if (mLastParent != null && mLastParent.getRootTask() != null) {
+ } else if (mLastKnownParent != null && mLastKnownParent.getRootTask() != null) {
task.getRootTask().mExitingActivities.remove(this);
}
final Task rootTask = getRootTask();
@@ -1362,7 +1374,11 @@
? rootTask.getAnimatingActivityRegistry()
: null;
- mLastParent = task;
+ mLastKnownParent = task;
+ if (mLastKnownParent == mLastParentBeforePip) {
+ // Activity's reparented back from pip, clear the links once established
+ clearLastParentBeforePip();
+ }
updateColorTransform();
@@ -1381,6 +1397,26 @@
}
}
+ /**
+ * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure
+ * {@link #getTask()} is set before this is called.
+ */
+ void setLastParentBeforePip() {
+ mLastParentBeforePip = getTask();
+ mLastParentBeforePip.mChildPipActivity = this;
+ }
+
+ private void clearLastParentBeforePip() {
+ if (mLastParentBeforePip != null) {
+ mLastParentBeforePip.mChildPipActivity = null;
+ mLastParentBeforePip = null;
+ }
+ }
+
+ @Nullable Task getLastParentBeforePip() {
+ return mLastParentBeforePip;
+ }
+
private void updateColorTransform() {
if (mSurfaceControl != null && mLastAppSaturationInfo != null) {
getPendingTransaction().setColorTransform(mSurfaceControl,
@@ -3434,6 +3470,7 @@
*/
void cleanUp(boolean cleanServices, boolean setState) {
task.cleanUpActivityReferences(this);
+ clearLastParentBeforePip();
deferRelaunchUntilPaused = false;
frozenBeforeDestroy = false;
@@ -5863,6 +5900,12 @@
nowVisible = true;
lastVisibleTime = SystemClock.uptimeMillis();
mAtmService.scheduleAppGcsLocked();
+ // The nowVisible may be false in onAnimationFinished because the transition animation
+ // was started by starting window but the main window hasn't drawn so the procedure
+ // didn't schedule. Hence also check when nowVisible becomes true (drawn) to avoid the
+ // closing activity having to wait until idle timeout to be stopped or destroyed if the
+ // next activity won't report idle (e.g. repeated view animation).
+ mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
}
}
@@ -6679,26 +6722,26 @@
// traverse the copy.
final ArrayList<WindowState> children = new ArrayList<>(mChildren);
children.forEach(WindowState::onExitAnimationDone);
+ // The starting window could transfer to another activity after app transition started, in
+ // that case the latest top activity might not receive exit animation done callback if the
+ // starting window didn't applied exit animation success. Notify animation finish to the
+ // starting window if needed.
+ if (task != null && startingMoved) {
+ final WindowState transferredStarting = task.getWindow(w ->
+ w.mAttrs.type == TYPE_APPLICATION_STARTING);
+ if (transferredStarting != null && transferredStarting.mAnimatingExit
+ && !transferredStarting.isSelfAnimating(0 /* flags */,
+ ANIMATION_TYPE_WINDOW_ANIMATION)) {
+ transferredStarting.onExitAnimationDone();
+ }
+ }
getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
scheduleAnimation();
- if (!mTaskSupervisor.mStoppingActivities.isEmpty()
- || !mTaskSupervisor.mFinishingActivities.isEmpty()) {
- if (mRootWindowContainer.allResumedActivitiesIdle()) {
- // If all activities are already idle then we now need to make sure we perform
- // the full stop of this activity. This is because we won't do that while they
- // are still waiting for the animation to finish.
- mTaskSupervisor.scheduleIdle();
- } else if (mRootWindowContainer.allResumedActivitiesVisible()) {
- // If all resumed activities are already visible (and should be drawn, see
- // updateReportedVisibility ~ nowVisible) but not idle, we still schedule to
- // process the stopping and finishing activities because the transition is done.
- // This also avoids if the next activity never reports idle (e.g. animating view),
- // the previous will need to wait until idle timeout to be stopped or destroyed.
- mTaskSupervisor.scheduleProcessStoppingAndFinishingActivities();
- }
- }
+ // Schedule to handle the stopping and finishing activities which the animation is done
+ // because the activities which were animating have not been stopped yet.
+ mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -7022,11 +7065,13 @@
// and back which can cause visible issues (see b/184078928).
final int parentWindowingMode =
newParentConfiguration.windowConfiguration.getWindowingMode();
+ final boolean isFixedOrientationLetterboxAllowed =
+ isSplitScreenWindowingMode(parentWindowingMode)
+ || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
+ || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
- if (isSplitScreenWindowingMode(parentWindowingMode)
- || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- || parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ if (isFixedOrientationLetterboxAllowed) {
resolveFixedOrientationConfiguration(newParentConfiguration);
}
@@ -7047,12 +7092,16 @@
resolveFullscreenConfiguration(newParentConfiguration);
}
+ if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
+ // In fullscreen, can be letterboxed for aspect ratio.
+ || !inMultiWindowMode()) {
+ updateResolvedBoundsHorizontalPosition(newParentConfiguration);
+ }
+
if (mVisibleRequested) {
updateCompatDisplayInsets();
}
- // TODO(b/175212232): Consolidate position logic from each "resolve" method above here.
-
// Assign configuration sequence number into hierarchy because there is a different way than
// ensureActivityConfiguration() in this class that uses configuration in WindowState during
// layout traversals.
@@ -7083,6 +7132,47 @@
}
}
+
+ /**
+ * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
+ * requested in the config or via an ADB command. For more context see {@link
+ * WindowManagerService#getLetterboxHorizontalPositionMultiplier}.
+ */
+ private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
+ final Configuration resolvedConfig = getResolvedOverrideConfiguration();
+ final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ final Rect screenResolvedBounds =
+ mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
+ final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) {
+ return;
+ }
+
+ int offsetX = 0;
+ if (screenResolvedBounds.width() >= parentAppBounds.width()) {
+ // If resolved bounds overlap with insets, center within app bounds.
+ offsetX = getHorizontalCenterOffset(
+ parentAppBounds.width(), screenResolvedBounds.width());
+ } else {
+ float positionMultiplier = mWmService.getLetterboxHorizontalPositionMultiplier();
+ positionMultiplier =
+ (positionMultiplier < 0.0f || positionMultiplier > 1.0f)
+ // Default to central position if invalid value is provided.
+ ? 0.5f : positionMultiplier;
+ offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
+ * positionMultiplier);
+ }
+
+ if (mSizeCompatBounds != null) {
+ mSizeCompatBounds.offset(offsetX, 0 /* offsetY */);
+ final int dx = mSizeCompatBounds.left - resolvedBounds.left;
+ offsetBounds(resolvedConfig, dx, 0 /* offsetY */);
+ } else {
+ offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+ }
+ }
+
/**
* Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed
* orientation then aspect ratio restrictions are also already respected.
@@ -7160,7 +7250,10 @@
resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height);
} else {
final int width = (int) Math.rint(parentHeight / aspect);
- final int left = parentBounds.centerX() - width / 2;
+ final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds();
+ final int left = width <= parentAppBounds.width()
+ // Avoid overlapping with the horizontal decor area when possible.
+ ? parentAppBounds.left : parentBounds.centerX() - width / 2;
resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom);
}
@@ -7212,12 +7305,6 @@
task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
getFixedRotationTransformDisplayInfo());
}
- if (needToBeCentered) {
- // Offset to center relative to parent's app bounds.
- final int offsetX = getHorizontalCenterOffset(
- parentAppBounds.width(), resolvedBounds.width());
- offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
- }
}
/**
@@ -7315,8 +7402,8 @@
final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds();
- // Calculates the scale and offset to horizontal center the size compatibility bounds into
- // the region which is available to application.
+ // Calculates the scale the size compatibility bounds into the region which is available
+ // to application.
final int contentW = resolvedAppBounds.width();
final int contentH = resolvedAppBounds.height();
final int viewportW = containerAppBounds.width();
@@ -7324,8 +7411,9 @@
// Only allow to scale down.
mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH)
? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH);
- final int screenTopInset = containerAppBounds.top - containerBounds.top;
- final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top;
+ final int containerTopInset = containerAppBounds.top - containerBounds.top;
+ final boolean topNotAligned =
+ containerTopInset != resolvedAppBounds.top - resolvedBounds.top;
if (mSizeCompatScale != 1f || topNotAligned) {
if (mSizeCompatBounds == null) {
mSizeCompatBounds = new Rect();
@@ -7334,18 +7422,15 @@
mSizeCompatBounds.offsetTo(0, 0);
mSizeCompatBounds.scale(mSizeCompatScale);
// The insets are included in height, e.g. the area of real cutout shouldn't be scaled.
- mSizeCompatBounds.bottom += screenTopInset;
+ mSizeCompatBounds.bottom += containerTopInset;
} else {
mSizeCompatBounds = null;
}
- // Center horizontally in parent (app bounds) and align to top of parent (bounds)
- // - this is a UX choice.
- final int offsetX = getHorizontalCenterOffset(
- (int) viewportW, (int) (contentW * mSizeCompatScale));
+ // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor
+ // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
- final int screenPosX = (fillContainer
- ? containerBounds.left : containerAppBounds.left) + offsetX;
+ final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
final int screenPosY = containerBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 75a188e..1158a9c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2721,8 +2721,22 @@
launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
- launchFlags &=
- ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK) {
+ // Remove MULTIPLE_TASK flag along with NEW_DOCUMENT only if NEW_DOCUMENT
+ // is set, otherwise we still want to keep the MULTIPLE_TASK flag (if
+ // any) for singleInstancePerTask that the multiple tasks can be created,
+ // or a singleInstancePerTask activity is basically the same as a
+ // singleTask activity when documentLaunchMode set to never.
+ if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
+ launchFlags &= ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ | FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ } else {
+ // TODO(b/184903976): Should FLAG_ACTIVITY_MULTIPLE_TASK always be
+ // removed for document-never activity?
+ launchFlags &=
+ ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index bdde369..2b1cf39 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2027,7 +2027,9 @@
}
final void scheduleIdle() {
- mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+ if (!mHandler.hasMessages(IDLE_NOW_MSG)) {
+ mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+ }
}
/**
@@ -2115,8 +2117,10 @@
}
}
- void scheduleProcessStoppingAndFinishingActivities() {
- if (!mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)) {
+ void scheduleProcessStoppingAndFinishingActivitiesIfNeeded() {
+ if ((!mStoppingActivities.isEmpty() || !mFinishingActivities.isEmpty())
+ && !mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)
+ && mRootWindowContainer.allResumedActivitiesVisible()) {
mHandler.sendEmptyMessage(PROCESS_STOPPING_AND_FINISHING_MSG);
}
}
diff --git a/services/core/java/com/android/server/wm/BlurController.java b/services/core/java/com/android/server/wm/BlurController.java
index 128d452..d920267 100644
--- a/services/core/java/com/android/server/wm/BlurController.java
+++ b/services/core/java/com/android/server/wm/BlurController.java
@@ -18,24 +18,52 @@
import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.view.ICrossWindowBlurEnabledListener;
import com.android.internal.annotations.GuardedBy;
+/**
+ * Keeps track of the different factors that determine whether cross-window blur is enabled
+ * or disabled. Also keeps a list of all interested listeners and notifies them when the
+ * blur enabled state changes.
+ */
final class BlurController {
-
+ private final PowerManager mPowerManager;
private final RemoteCallbackList<ICrossWindowBlurEnabledListener>
mBlurEnabledListeners = new RemoteCallbackList<>();
+ // We don't use the WM global lock, because the BlurController is not involved in window
+ // drawing and only receives binder calls that don't need synchronization with the rest of WM
private final Object mLock = new Object();
@GuardedBy("mLock")
boolean mBlurEnabled;
@GuardedBy("mLock")
boolean mBlurForceDisabled;
+ @GuardedBy("mLock")
+ boolean mInBatterySaverMode;
- BlurController() {
- mBlurEnabled = CROSS_WINDOW_BLUR_SUPPORTED;
+ BlurController(Context context, PowerManager powerManager) {
+ mPowerManager = powerManager;
+ mInBatterySaverMode = mPowerManager.isPowerSaveMode();
+ updateBlurEnabledLocked();
+
+ IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ context.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(intent.getAction())) {
+ setBatterySaverEnabled(mPowerManager.isPowerSaveMode());
+ }
+ }
+ }, filter, null, null);
}
boolean registerCrossWindowBlurEnabledListener(ICrossWindowBlurEnabledListener listener) {
@@ -59,8 +87,16 @@
}
+ void setBatterySaverEnabled(boolean enabled) {
+ synchronized (mLock) {
+ mInBatterySaverMode = enabled;
+ updateBlurEnabledLocked();
+ }
+ }
+
private void updateBlurEnabledLocked() {
- final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled;
+ final boolean newEnabled = CROSS_WINDOW_BLUR_SUPPORTED && !mBlurForceDisabled
+ && !mInBatterySaverMode;
if (mBlurEnabled == newEnabled) {
return;
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 6631a3e..4bb48b0b 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1415,6 +1415,9 @@
return true;
}
+ // The given task if always treated as in visible range if it is the origin of pinned task.
+ if (task.mChildPipActivity != null) return true;
+
if (mMaxNumVisibleTasks >= 0) {
// Always keep up to the max number of recent tasks, but return false afterwards
return numVisibleTasks <= mMaxNumVisibleTasks;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 52551ec..b9fc8b1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2124,6 +2124,8 @@
.setDeferTaskAppear(true)
.setHasBeenVisible(true)
.build();
+ // Establish bi-directional link between the original and pinned task.
+ r.setLastParentBeforePip();
// It's possible the task entering PIP is in freeform, so save the last
// non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore
// to its previous freeform bounds.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 8690499..858d9f3 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -131,6 +131,7 @@
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
import static com.android.server.wm.TaskProto.DISPLAY_ID;
import static com.android.server.wm.TaskProto.FILLS_PARENT;
+import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY;
import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
import static com.android.server.wm.TaskProto.MIN_HEIGHT;
import static com.android.server.wm.TaskProto.MIN_WIDTH;
@@ -836,6 +837,14 @@
// The task will be removed when TaskOrganizer, which is managing the task, is destroyed.
boolean mRemoveWithTaskOrganizer;
+ /**
+ * Reference to the pinned activity that is logically parented to this task, ie.
+ * the previous top activity within this task is put into pinned mode.
+ * This always gets cleared in pair with the ActivityRecord-to-Task link as seen in
+ * {@link ActivityRecord#clearLastParentBeforePip()}.
+ */
+ ActivityRecord mChildPipActivity;
+
private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset,
@@ -1841,6 +1850,10 @@
/** Completely remove all activities associated with an existing task. */
void performClearTask(String reason) {
+ // The original task is to be removed, try remove also the pinned task.
+ if (mChildPipActivity != null && mChildPipActivity.getTask() != null) {
+ mTaskSupervisor.removeRootTask(mChildPipActivity.getTask());
+ }
// Broken down into to cases to avoid object create due to capturing mStack.
if (getRootTask() == null) {
forAllActivities((r) -> {
@@ -4449,6 +4462,7 @@
}
pw.print(prefix); pw.print("taskId=" + mTaskId);
pw.println(" rootTaskId=" + getRootTaskId());
+ pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
pw.print(prefix); pw.print("mHasBeenVisible="); pw.println(getHasBeenVisible());
pw.print(prefix); pw.print("mResizeMode=");
pw.print(ActivityInfo.resizeModeToString(mResizeMode));
@@ -5328,7 +5342,6 @@
return;
}
final int currentMode = getWindowingMode();
- final int currentOverrideMode = getRequestedOverrideWindowingMode();
final Task topTask = getTopMostTask();
int windowingMode = preferredWindowingMode;
@@ -5397,9 +5410,26 @@
mTaskSupervisor.mNoAnimActivities.add(topActivity);
}
super.setWindowingMode(windowingMode);
- // setWindowingMode triggers an onConfigurationChanged cascade which can result in a
- // different resolved windowing mode (usually when preferredWindowingMode is UNDEFINED).
- windowingMode = getWindowingMode();
+
+ // Try reparent pinned activity back to its original task after onConfigurationChanged
+ // cascade finishes. This is done on Task level instead of
+ // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP,
+ // we set final windowing mode on the ActivityRecord first and then on its Task when
+ // the exit PiP transition finishes. Meanwhile, the exit transition is always
+ // performed on its original task, reparent immediately in ActivityRecord breaks it.
+ if (currentMode == WINDOWING_MODE_PINNED) {
+ if (topActivity != null && topActivity.getLastParentBeforePip() != null) {
+ // Do not reparent if the pinned task is in removal, indicated by the
+ // force hidden flag.
+ if (!isForceHidden()) {
+ final Task lastParentBeforePip = topActivity.getLastParentBeforePip();
+ topActivity.reparent(lastParentBeforePip,
+ lastParentBeforePip.getChildCount() /* top */,
+ "movePinnedActivityToOriginalTask");
+ lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
+ }
+ }
+ }
if (creating) {
// Nothing else to do if we don't have a window container yet. E.g. call from ctor.
@@ -6279,6 +6309,8 @@
// Launching this app's activity, make sure the app is no longer
// considered stopped.
try {
+ mTaskSupervisor.getActivityMetricsLogger()
+ .notifyBeforePackageUnstopped(next.packageName);
mAtmService.getPackageManager().setPackageStoppedState(
next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
} catch (RemoteException e1) {
@@ -7530,7 +7562,11 @@
final Task task = getBottomMostTask();
setWindowingMode(WINDOWING_MODE_UNDEFINED);
- getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+ // Task could have been removed from the hierarchy due to windowing mode change
+ // where its only child is reparented back to their original parent task.
+ if (isAttached()) {
+ getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */);
+ }
mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this);
});
@@ -7831,6 +7867,7 @@
proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer);
proto.write(AFFINITY, affinity);
+ proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 5ffab48..a467d82 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -646,8 +646,12 @@
if (shouldDisableSnapshots()) {
return;
}
+ final DisplayContent displayContent = mService.mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ return;
+ }
mTmpTasks.clear();
- mService.mRoot.getDisplayContent(displayId).forAllTasks(task -> {
+ displayContent.forAllTasks(task -> {
// Since RecentsAnimation will handle task snapshot while switching apps with the best
// capture timing (e.g. IME window capture), No need additional task capture while task
// is controlled by RecentsAnimation.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 7650fa1..0c9473a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -731,7 +731,7 @@
final TaskSnapshotController mTaskSnapshotController;
- final BlurController mBlurController = new BlurController();
+ final BlurController mBlurController;
boolean mIsTouchDevice;
boolean mIsFakeTouchDevice;
@@ -1024,6 +1024,10 @@
// Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead.
private float mLetterboxBackgroundWallpaperDarkScrimAlpha;
+ // horizontal position of a center of the letterboxed app window. 0 corresponds to the left
+ // side of the screen and 1.0 to the right side.
+ private float mLetterboxHorizontalPositionMultiplier;
+
final InputManagerService mInputManager;
final DisplayManagerInternal mDisplayManagerInternal;
final DisplayManager mDisplayManager;
@@ -1265,6 +1269,8 @@
com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat(
com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+ mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -1418,6 +1424,8 @@
setGlobalShadowSettings();
mAnrController = new AnrController(this);
mStartingSurfaceController = new StartingSurfaceController(this);
+
+ mBlurController = new BlurController(mContext, mPowerManager);
}
DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() {
@@ -1740,8 +1748,11 @@
}
// Switch to listen to the {@link WindowToken token}'s configuration changes when
- // adding a window to the window context.
- if (mWindowContextListenerController.hasListener(windowContextToken)) {
+ // adding a window to the window context. Filter sub window type here because the sub
+ // window must be attached to the parent window, which is attached to the window context
+ // created window token.
+ if (!win.isChildWindow()
+ && mWindowContextListenerController.hasListener(windowContextToken)) {
final int windowContextType = mWindowContextListenerController
.getWindowType(windowContextToken);
if (type != windowContextType) {
@@ -4053,6 +4064,38 @@
return mLetterboxBackgroundWallpaperBlurRadius;
}
+ /*
+ * Gets horizontal position of a center of the letterboxed app window specified
+ * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}
+ * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
+ * right side.
+ *
+ * <p>This value can be outside of [0, 1] range so clients need to check and default to the
+ * central position (0.5).
+ */
+ float getLetterboxHorizontalPositionMultiplier() {
+ return mLetterboxHorizontalPositionMultiplier;
+ }
+
+ /**
+ * Overrides horizontal position of a center of the letterboxed app window. If given value < 0
+ * or > 1, then it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
+ * central position (0.5) is used.
+ */
+ void setLetterboxHorizontalPositionMultiplier(float multiplier) {
+ mLetterboxHorizontalPositionMultiplier = multiplier;
+ }
+
+ /**
+ * Resets horizontal position of a center of the letterboxed app window to {@link
+ * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
+ */
+ void resetLetterboxHorizontalPositionMultiplier() {
+ mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
+ }
+
@Override
public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
if (!checkCallingPermission(
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 5942f34..68257d4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -143,6 +143,10 @@
return runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
case "get-letterbox-background-wallpaper-dark-scrim-alpha":
return runGetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+ case "set-letterbox-horizontal-position-multiplier":
+ return runSeLetterboxHorizontalPositionMultiplier(pw);
+ case "get-letterbox-horizontal-position-multiplier":
+ return runGetLetterboxHorizontalPositionMultiplier(pw);
case "set-sandbox-display-apis":
return runSandboxDisplayApis(pw);
case "reset":
@@ -846,6 +850,43 @@
return 0;
}
+ private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+ final float multiplier;
+ try {
+ String arg = getNextArgRequired();
+ if ("reset".equals(arg)) {
+ synchronized (mInternal.mGlobalLock) {
+ mInternal.resetLetterboxHorizontalPositionMultiplier();
+ }
+ return 0;
+ }
+ multiplier = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad multiplier format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'reset' or multiplier should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mInternal.setLetterboxHorizontalPositionMultiplier(multiplier);
+ }
+ return 0;
+ }
+
+ private int runGetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+ synchronized (mInternal.mGlobalLock) {
+ final float multiplier = mInternal.getLetterboxHorizontalPositionMultiplier();
+ if (multiplier < 0) {
+ pw.println("Letterbox horizontal position multiplier is not set");
+ } else {
+ pw.println("Letterbox horizontal position multiplier is " + multiplier);
+ }
+ }
+ return 0;
+ }
+
private int runReset(PrintWriter pw) throws RemoteException {
int displayId = getDisplayId(getNextArg());
@@ -888,6 +929,9 @@
// set-letterbox-background-wallpaper-dark-scrim-alpha
mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ // set-letterbox-horizontal-position-multiplier
+ mInternal.resetLetterboxHorizontalPositionMultiplier();
+
// set-sandbox-display-apis
mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
@@ -954,6 +998,11 @@
pw.println(" letterbox background. If alpha < 0 or >= 1 both it and");
pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored and ");
pw.println(" 0.0 (transparent) is used instead.");
+ pw.println(" set-letterbox-horizontal-position-multiplier [reset|multiplier]");
+ pw.println(" get-letterbox-horizontal-position-multiplier");
+ pw.println(" horizontal position of a center of a letterboxed app. If it < 0 or > 1");
+ pw.println(" then both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+ pw.println(" are ignored and central position (0.5) is used.");
pw.println(" set-sandbox-display-apis [true|1|false|0]");
pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ddcb2bf..cf9b88a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -8517,20 +8517,16 @@
+ " as profile owner for user " + userHandle);
return false;
}
- if (who == null
- || !isPackageInstalledForUser(who.getPackageName(), userHandle)) {
- throw new IllegalArgumentException("Component " + who
- + " not installed for userId:" + userHandle);
- }
+ Preconditions.checkArgument(who != null);
final CallerIdentity caller = getCallerIdentity();
synchronized (getLockObject()) {
enforceCanSetProfileOwnerLocked(caller, who, userHandle);
-
+ Preconditions.checkArgument(isPackageInstalledForUser(who.getPackageName(), userHandle),
+ "Component " + who + " not installed for userId:" + userHandle);
final ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle);
- if (admin == null || getUserData(userHandle).mRemovingAdmins.contains(who)) {
- throw new IllegalArgumentException("Not active admin: " + who);
- }
+ Preconditions.checkArgument(admin != null && !getUserData(
+ userHandle).mRemovingAdmins.contains(who), "Not active admin: " + who);
final int parentUserId = getProfileParentId(userHandle);
// When trying to set a profile owner on a new user, it may be that this user is
diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp
index b136d00..83677c2 100644
--- a/services/tests/PackageManagerServiceTests/host/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/Android.bp
@@ -36,6 +36,10 @@
],
test_suites: ["general-tests"],
java_resources: [
+ ":PackageManagerTestOverlayActor",
+ ":PackageManagerTestOverlay",
+ ":PackageManagerTestOverlayTarget",
+ ":PackageManagerTestOverlayTargetNoOverlayable",
":PackageManagerTestAppDeclaresStaticLibrary",
":PackageManagerTestAppStub",
":PackageManagerTestAppUsesStaticLibrary",
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
new file mode 100644
index 0000000..558d01ed
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/OverlayActorVisibilityTest.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.test
+
+import com.android.internal.util.test.SystemPreparer
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import java.io.File
+
+@RunWith(DeviceJUnit4ClassRunner::class)
+class OverlayActorVisibilityTest : BaseHostJUnit4Test() {
+
+ companion object {
+ private const val ACTOR_PKG_NAME = "com.android.server.pm.test.overlay.actor"
+ private const val ACTOR_APK = "PackageManagerTestOverlayActor.apk"
+ private const val TARGET_APK = "PackageManagerTestOverlayTarget.apk"
+ private const val OVERLAY_APK = "PackageManagerTestOverlay.apk"
+ private const val TARGET_NO_OVERLAYABLE_APK =
+ "PackageManagerTestOverlayTargetNoOverlayable.apk"
+
+ @get:ClassRule
+ val deviceRebootRule = SystemPreparer.TestRuleDelegate(true)
+ }
+
+ @get:Rule
+ val tempFolder = TemporaryFolder()
+
+ private val preparer: SystemPreparer = SystemPreparer(
+ tempFolder,
+ SystemPreparer.RebootStrategy.FULL,
+ deviceRebootRule
+ ) { this.device }
+
+ private val namedActorFile = File(
+ "/system/etc/sysconfig/com.android.server.pm.test.OverlayActorVisibilityTest.xml"
+ )
+
+ @Before
+ @After
+ fun uninstallPackages() {
+ device.uninstallPackages(ACTOR_APK, TARGET_APK, OVERLAY_APK)
+ }
+
+ @Before
+ fun pushSysConfigFile() {
+ // In order for the test app to be the verification agent, it needs a permission file
+ // which can be pushed onto the system and removed afterwards.
+ // language=XML
+ val file = tempFolder.newFile().apply {
+ """
+ <config>
+ <named-actor
+ namespace="androidTest"
+ name="OverlayActorVisibilityTest"
+ package="$ACTOR_PKG_NAME"
+ />
+ </config>
+ """
+ .trimIndent()
+ .let { writeText(it) }
+ }
+
+ preparer.pushFile(file, namedActorFile.toString())
+ .reboot()
+ }
+
+ @After
+ fun deleteSysConfigFile() {
+ preparer.deleteFile(namedActorFile.toString())
+ .reboot()
+ }
+
+ @Test
+ fun testVisibilityByOverlayable() {
+ assertThat(device.installJavaResourceApk(tempFolder, ACTOR_APK, false)).isNull()
+ assertThat(device.installJavaResourceApk(tempFolder, OVERLAY_APK, false)).isNull()
+ assertThat(device.installJavaResourceApk(tempFolder, TARGET_NO_OVERLAYABLE_APK, false))
+ .isNull()
+
+ runDeviceTests(
+ ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+ "verifyNotVisible"
+ )
+
+ assertThat(device.installJavaResourceApk(tempFolder, TARGET_APK, true)).isNull()
+
+ assertWithMessage(device.executeShellCommand("dumpsys package $OVERLAY_APK"))
+
+ runDeviceTests(
+ ACTOR_PKG_NAME, "$ACTOR_PKG_NAME.OverlayableVisibilityTest",
+ "verifyVisible"
+ )
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
new file mode 100644
index 0000000..92dcd34
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/Android.bp
@@ -0,0 +1,26 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "PackageManagerTestOverlay",
+ srcs: [
+ "src/**/*.kt",
+ ],
+}
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
similarity index 64%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
index b8ea622..21e4432 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.overlay">
+ <application/>
+ <overlay android:targetPackage="com.android.server.pm.test.overlay.target"
+ android:targetName="Testing"/>
+</manifest>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
index b8ea622..f0b8586 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/res/values/values.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<resources>
+ <string name="policy_public">You have been overlaid</string>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
new file mode 100644
index 0000000..5718474
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+ // 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_test_helper_app {
+ name: "PackageManagerTestOverlayActor",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "androidx.test.runner",
+ "junit",
+ "kotlin-test",
+ "truth-prebuilt",
+ ],
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
new file mode 100644
index 0000000..a92a14f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.server.pm.test.overlay.actor"
+ >
+
+ <application/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.pm.test.overlay.actor"
+ />
+
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
new file mode 100644
index 0000000..7537247f
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayActor/src/com/android/server/pm/test/overlay/actor/OverlayableVisibilityTest.kt
@@ -0,0 +1,51 @@
+/*
+ * 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.test.overlay.actor
+
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import kotlin.test.assertFailsWith
+
+class OverlayableVisibilityTest {
+
+ companion object {
+ private const val TARGET_PACKAGE = "com.android.server.pm.test.overlay.target"
+ private const val OVERLAY_PACKAGE = "com.android.server.pm.test.overlay"
+ }
+
+ private val context: Context = InstrumentationRegistry.getContext()
+ private val packageManager = context.packageManager
+
+ @Test
+ fun verifyVisible() {
+ assertThat(packageManager.getApplicationInfo(TARGET_PACKAGE, 0)).isNotNull()
+ assertThat(packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)).isNotNull()
+ }
+
+ @Test
+ fun verifyNotVisible() {
+ assertFailsWith(PackageManager.NameNotFoundException::class) {
+ packageManager.getApplicationInfo(TARGET_PACKAGE, 0)
+ }
+ assertFailsWith(PackageManager.NameNotFoundException::class) {
+ packageManager.getApplicationInfo(OVERLAY_PACKAGE, 0)
+ }
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
new file mode 100644
index 0000000..2bb6b82
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "PackageManagerTestOverlayTarget",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ resource_dirs: [
+ "res",
+ "res_overlayable",
+ ],
+}
+
+android_test_helper_app {
+ name: "PackageManagerTestOverlayTargetNoOverlayable",
+ defaults: ["cts_support_defaults"],
+ sdk_version: "current",
+ resource_dirs: [
+ "res",
+ ],
+}
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
index b8ea622..6038cb1 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.pm.test.overlay.target">
+</manifest>
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
new file mode 100644
index 0000000..4625e3b
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/assets/asset.txt
@@ -0,0 +1 @@
+Not overlaid
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
index b8ea622..283f5c1 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<resources>
+ <public-group type="string" first-id="0x7f010000">
+ <public name="policy_public" />
+ </public-group>
+</resources>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
similarity index 68%
copy from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
copy to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
index b8ea622..822194f 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<resources>
+ <string name="policy_public">Not overlaid</string>
+</resources>
diff --git a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
similarity index 68%
rename from packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
rename to services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
index b8ea622..0100389 100644
--- a/packages/SystemUI/res/drawable/qs_footer_drag_handle.xml
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
+<!-- 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.
@@ -13,10 +13,11 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<shape
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:shape="rectangle" >
- <solid
- android:color="?android:attr/textColorSecondary" />
- <corners android:radius="2dp" />
-</shape>
+
+<resources>
+ <overlayable name="Testing" actor="overlay://androidTest/OverlayActorVisibilityTest">
+ <policy type="public">
+ <item type="string" name="policy_public" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index dce853a..4de8d52 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -235,9 +235,23 @@
<category android:name="android.intent.category.BROWSABLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="https"/>
- <data android:path="/sub5"/>
- <data android:host="example5.com"/>
- <data android:host="invalid5"/>
+ <data android:path="/sub6"/>
+ <data android:host="example6.com"/>
+ <data android:host="invalid6"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="example7.com"/>
+ <intent-filter android:autoVerify="$autoVerify">
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="https"/>
+ </intent-filter>
+ <intent-filter android:autoVerify="$autoVerify">
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:path="/sub7"/>
</intent-filter>
</xml>
""".trimIndent()
@@ -324,6 +338,30 @@
addDataAuthority("invalid6", null)
}
)
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataAuthority("example7.com", null)
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataScheme("https")
+ }
+ )
+ addIntent(
+ ParsedIntentInfo().apply {
+ setAutoVerify(autoVerify)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL)
+ }
+ )
},
)
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
index 14c02d5..0a5a3bf 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationJavaUtil.java
@@ -20,11 +20,14 @@
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
+import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainVerificationManager;
import com.android.server.pm.verify.domain.DomainVerificationService;
+import java.util.List;
import java.util.Set;
+import java.util.SortedSet;
import java.util.UUID;
/**
@@ -58,4 +61,14 @@
throws PackageManager.NameNotFoundException {
return manager.setDomainVerificationUserSelection(domainSetId, domains, enabled);
}
+
+ static SortedSet<DomainOwner> getOwnersForDomain(@NonNull DomainVerificationManager manager,
+ @Nullable String domain) {
+ return manager.getOwnersForDomain(domain);
+ }
+
+ static List<DomainOwner> getOwnersForDomain(@NonNull DomainVerificationService service,
+ @Nullable String domain, @UserIdInt int userId) {
+ return service.getOwnersForDomain(domain, userId);
+ }
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 7fea4ee..3838f68 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -292,8 +292,17 @@
val manager0 = makeManager(service, 0)
val manager1 = makeManager(service, 1)
- assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
- assertThat(manager0.getOwnersForDomain(DOMAIN_1)).isEmpty()
+ listOf(DOMAIN_1, "").forEach {
+ assertThat(service.getOwnersForDomain(it, 0)).isEmpty()
+ assertThat(manager0.getOwnersForDomain(it)).isEmpty()
+ }
+
+ assertFailsWith(NullPointerException::class) {
+ DomainVerificationJavaUtil.getOwnersForDomain(service, null, 0)
+ }
+ assertFailsWith(NullPointerException::class) {
+ DomainVerificationJavaUtil.getOwnersForDomain(manager0, null)
+ }
assertThat(
service.setStatus(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
new file mode 100644
index 0000000..98634b2
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.test.verify.domain
+
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import com.android.server.pm.verify.domain.DomainVerificationUtils
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@RunWith(Parameterized::class)
+class DomainVerificationValidIntentTest {
+
+ companion object {
+
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun parameters(): Array<Params> {
+ val succeeding = mutableListOf<Params>()
+ val failing = mutableListOf<Params>()
+
+ // Start with the base intent
+ val base = Params(categorySet = emptySet()).also { succeeding += it }
+
+ // Add all explicit supported categorySet
+ succeeding += base.copy(
+ categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+ matchDefaultOnly = true
+ )
+
+ failing += base.copy(
+ categorySet = setOf(Intent.CATEGORY_BROWSABLE),
+ matchDefaultOnly = false
+ )
+
+ succeeding += listOf(true, false).map {
+ base.copy(
+ categorySet = setOf(Intent.CATEGORY_DEFAULT),
+ matchDefaultOnly = it
+ )
+ }
+
+ succeeding += listOf(true, false).map {
+ base.copy(
+ categorySet = setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT),
+ matchDefaultOnly = it
+ )
+ }
+
+ // Fail on unsupported category
+ failing += listOf(
+ emptySet(),
+ setOf(Intent.CATEGORY_BROWSABLE),
+ setOf(Intent.CATEGORY_DEFAULT),
+ setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT)
+ ).map { base.copy(categorySet = it + "invalid.CATEGORY") }
+
+ // Fail on unsupported action
+ failing += base.copy(action = Intent.ACTION_SEND)
+
+ // Fail on unsupported domain
+ failing += base.copy(domain = "invalid")
+
+ // Fail on empty domains
+ failing += base.copy(domain = "")
+
+ // Fail on missing scheme
+ failing += base.copy(
+ uriFunction = { Uri.Builder().authority("test.com").build() }
+ )
+
+ // Fail on missing host
+ failing += base.copy(
+ domain = "",
+ uriFunction = { Uri.Builder().scheme("https").build() }
+ )
+
+ succeeding.forEach { it.expected = true }
+ failing.forEach { it.expected = false }
+ return (succeeding + failing).toTypedArray()
+ }
+
+ data class Params(
+ val action: String = Intent.ACTION_VIEW,
+ val categorySet: Set<String> = mutableSetOf(),
+ val domain: String = "test.com",
+ val matchDefaultOnly: Boolean = true,
+ var expected: Boolean? = null,
+ val uriFunction: (domain: String) -> Uri = { Uri.parse("https://$it") }
+ ) {
+ val intent = Intent(action, uriFunction(domain)).apply {
+ categorySet.forEach(::addCategory)
+ }
+
+ override fun toString() = intent.toShortString(false, false, false, false) +
+ ", matchDefaultOnly = $matchDefaultOnly, expected = $expected"
+ }
+ }
+
+ @Parameterized.Parameter(0)
+ lateinit var params: Params
+
+ @Test
+ fun verify() {
+ val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0
+ assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags))
+ .isEqualTo(params.expected)
+ }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index e4b650c..17a5dcc 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,8 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission
+ android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index edfc21d..d55bbd1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -468,8 +468,9 @@
TEST_CALLING_UID);
}
- private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) {
- mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test",
+ private void setPrioritizedAlarm(int type, long triggerTime, long windowLength,
+ IAlarmListener listener) {
+ mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test",
FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID,
TEST_CALLING_PACKAGE, null);
}
@@ -1685,7 +1686,7 @@
final int numAlarms = 10;
for (int i = 0; i < numAlarms; i++) {
setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
- new IAlarmListener.Stub() {
+ 0, new IAlarmListener.Stub() {
@Override
public void doAlarm(IAlarmCompleteListener callback)
throws RemoteException {
@@ -1720,7 +1721,7 @@
final int numAlarms = 10;
for (int i = 0; i < numAlarms; i++) {
setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i,
- new IAlarmListener.Stub() {
+ 0, new IAlarmListener.Stub() {
@Override
public void doAlarm(IAlarmCompleteListener callback)
throws RemoteException {
@@ -1738,12 +1739,12 @@
}
assertEquals(numAlarms, alarmsFired.get());
- setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() {
@Override
public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
}
});
- setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() {
+ setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() {
@Override
public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
}
@@ -2223,7 +2224,11 @@
}
@Test
- public void minWindow() {
+ public void minWindowChangeEnabled() {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(
+ eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
+ anyString(), any(UserHandle.class)));
final long minWindow = 73;
setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
@@ -2239,6 +2244,48 @@
}
@Test
+ public void minWindowChangeDisabled() {
+ doReturn(false).when(
+ () -> CompatChanges.isChangeEnabled(
+ eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
+ anyString(), any(UserHandle.class)));
+ final long minWindow = 73;
+ setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+
+ // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC.
+ for (int window = 1; window <= minWindow; window++) {
+ final PendingIntent pi = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME, 0, window, pi, 0, 0, TEST_CALLING_UID, null);
+
+ assertEquals(1, mService.mAlarmStore.size());
+ final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+ assertEquals(window, a.windowLength);
+ }
+ }
+
+ @Test
+ public void minWindowPriorityAlarm() {
+ doReturn(true).when(
+ () -> CompatChanges.isChangeEnabled(
+ eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS),
+ anyString(), any(UserHandle.class)));
+ final long minWindow = 73;
+ setDeviceConfigLong(KEY_MIN_WINDOW, minWindow);
+
+ // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC.
+ for (int window = 1; window <= minWindow; window++) {
+ setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ });
+ assertEquals(1, mService.mAlarmStore.size());
+ final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0);
+ assertEquals(window, a.windowLength);
+ }
+ }
+
+ @Test
public void denyListPackagesAdded() {
mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"});
setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5");
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
new file mode 100644
index 0000000..a8d8a90
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -0,0 +1,488 @@
+/*
+ * 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.app;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.GameManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Supplier;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class GameManagerServiceTests {
+ @Mock MockContext mMockContext;
+ private static final String TAG = "GameServiceTests";
+ private static final String PACKAGE_NAME_INVALID = "com.android.app";
+ private static final int USER_ID_1 = 1001;
+ private static final int USER_ID_2 = 1002;
+
+ private MockitoSession mMockingSession;
+ private String mPackageName;
+ @Mock
+ private PackageManager mMockPackageManager;
+
+ // Stolen from ConnectivityServiceTest.MockContext
+ class MockContext extends ContextWrapper {
+ private static final String TAG = "MockContext";
+
+ // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
+ private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
+
+ MockContext(Context base) {
+ super(base);
+ }
+
+ /**
+ * Mock checks for the specified permission, and have them behave as per {@code granted}.
+ *
+ * <p>Passing null reverts to default behavior, which does a real permission check on the
+ * test package.
+ *
+ * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
+ * {@link PackageManager#PERMISSION_DENIED}.
+ */
+ public void setPermission(String permission, Integer granted) {
+ mMockedPermissions.put(permission, granted);
+ }
+
+ private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+ final Integer granted = mMockedPermissions.get(permission);
+ return granted != null ? granted : ifAbsent.get();
+ }
+
+ @Override
+ public int checkPermission(String permission, int pid, int uid) {
+ return checkMockedPermission(
+ permission, () -> super.checkPermission(permission, pid, uid));
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkMockedPermission(
+ permission, () -> super.checkCallingOrSelfPermission(permission));
+ }
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ final Integer granted = mMockedPermissions.get(permission);
+ if (granted == null) {
+ super.enforceCallingOrSelfPermission(permission, message);
+ return;
+ }
+
+ if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
+ throw new SecurityException("[Test] permission denied: " + permission);
+ }
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mMockPackageManager;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(DeviceConfig.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ mMockContext = new MockContext(InstrumentationRegistry.getContext());
+ mPackageName = mMockContext.getPackageName();
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = mPackageName;
+ final List<PackageInfo> packages = new ArrayList<>();
+ packages.add(pi);
+ when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void mockModifyGameModeGranted() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
+ private void mockModifyGameModeDenied() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
+ PackageManager.PERMISSION_DENIED);
+ }
+
+ private void mockDeviceConfigDefault() {
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigNone() {
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigPerformance() {
+ String configString = "mode=2,downscaleFactor=0.5";
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigBattery() {
+ String configString = "mode=3,downscaleFactor=0.7";
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigAll() {
+ String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5";
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigInvalid() {
+ String configString = "mode=2,downscaleFactor=0.55";
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ private void mockDeviceConfigMalformed() {
+ String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg";
+ DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder(
+ DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build();
+ when(DeviceConfig.getProperties(anyString(), anyString()))
+ .thenReturn(properties);
+ }
+
+ /**
+ * By default game mode is not supported.
+ */
+ @Test
+ public void testGameModeDefaultValue() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test the default behaviour for a nonexistent user.
+ */
+ @Test
+ public void testDefaultValueForNonexistentUser() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_2));
+ }
+
+ /**
+ * Test getter and setter of game modes.
+ */
+ @Test
+ public void testGameMode() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ mockModifyGameModeGranted();
+
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testGetGameModeInvalidPackageName() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ try {
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
+ USER_ID_1));
+
+ fail("GameManagerService failed to generate SecurityException when "
+ + "permission.MANAGE_GAME_MODE is not granted.");
+ } catch (SecurityException ignored) {
+ }
+
+ // The test should throw an exception, so the test is passing if we get here.
+ }
+
+ /**
+ * Test permission.MANAGE_GAME_MODE is checked
+ */
+ @Test
+ public void testSetGameModePermissionDenied() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+
+ // Update the game mode so we can read back something valid.
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
+ mockModifyGameModeDenied();
+ try {
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_1);
+
+ fail("GameManagerService failed to generate SecurityException when "
+ + "permission.MANAGE_GAME_MODE is denied.");
+ } catch (SecurityException ignored) {
+ }
+
+ // The test should throw an exception, so the test is passing if we get here.
+ mockModifyGameModeGranted();
+ // Verify that the Game Mode value wasn't updated.
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
+ /**
+ * Test game modes are user-specific.
+ */
+ @Test
+ public void testGameModeMultipleUsers() {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.onUserStarting(USER_ID_2);
+
+ mockModifyGameModeGranted();
+
+ // Set User 1 to Standard
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Set User 2 to Performance and verify User 1 is still Standard
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
+ USER_ID_2);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_2));
+ assertEquals(GameManager.GAME_MODE_STANDARD,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ // Set User 1 to Battery and verify User 2 is still Performance
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
+ USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_BATTERY,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_2));
+ }
+
+ /**
+ * Phonesky device config exists, but is only propagating the default value.
+ */
+ @Test
+ public void testDeviceConfigDefault() {
+ mockDeviceConfigDefault();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ assertEquals(modes.length, 1);
+ assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+ }
+
+ /**
+ * Phonesky device config does not exists.
+ */
+ @Test
+ public void testDeviceConfigNone() {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ assertEquals(modes.length, 1);
+ assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+ }
+
+ /**
+ * Phonesky device config for performance mode exists and is valid.
+ */
+ @Test
+ public void testDeviceConfigPerformance() {
+ mockDeviceConfigPerformance();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ boolean perfModeExists = false;
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ for (int mode : modes) {
+ if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+ perfModeExists = true;
+ }
+ }
+ assertEquals(modes.length, 1);
+ assertTrue(perfModeExists);
+ }
+
+ /**
+ * Phonesky device config for battery mode exists and is valid.
+ */
+ @Test
+ public void testDeviceConfigBattery() {
+ mockDeviceConfigBattery();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ boolean batteryModeExists = false;
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ for (int mode : modes) {
+ if (mode == GameManager.GAME_MODE_BATTERY) {
+ batteryModeExists = true;
+ }
+ }
+ assertEquals(modes.length, 1);
+ assertTrue(batteryModeExists);
+ }
+
+ /**
+ * Phonesky device configs for both battery and performance modes exists and are valid.
+ */
+ @Test
+ public void testDeviceConfigAll() {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ boolean batteryModeExists = false;
+ boolean perfModeExists = false;
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ for (int mode : modes) {
+ if (mode == GameManager.GAME_MODE_BATTERY) {
+ batteryModeExists = true;
+ } else if (mode == GameManager.GAME_MODE_PERFORMANCE) {
+ perfModeExists = true;
+ }
+ }
+ assertTrue(batteryModeExists);
+ assertTrue(perfModeExists);
+ }
+
+ /**
+ * Phonesky device config contains values that parse correctly but are not valid in game mode.
+ */
+ @Test
+ public void testDeviceConfigInvalid() {
+ mockDeviceConfigInvalid();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ assertEquals(modes.length, 1);
+ assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+ }
+
+ /**
+ * Phonesky device config is garbage.
+ */
+ @Test
+ public void testDeviceConfigMalformed() {
+ mockDeviceConfigMalformed();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ gameManagerService.loadDeviceConfigLocked();
+
+ int[] modes = gameManagerService.getAvailableGameModes(mPackageName);
+ assertEquals(modes.length, 1);
+ assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index 924ad7f..76f7e80 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -35,7 +35,6 @@
import android.os.Message;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
import android.util.LongSparseArray;
import androidx.test.InstrumentationRegistry;
@@ -68,7 +67,6 @@
private File mBlobsDir;
private LongSparseArray<BlobStoreSession> mUserSessions;
- private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
private static final String TEST_PKG1 = "com.example1";
private static final String TEST_PKG2 = "com.example2";
@@ -99,10 +97,8 @@
mHandler = new TestHandler(Looper.getMainLooper());
mService = new BlobStoreManagerService(mContext, new TestInjector());
mUserSessions = new LongSparseArray<>();
- mUserBlobs = new ArrayMap<>();
mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
- mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
}
@After
@@ -147,7 +143,7 @@
final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1,
blobHandle1, true /* hasLeases */);
doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1);
- mUserBlobs.put(blobHandle1, blobMetadata1);
+ addBlob(blobHandle1, blobMetadata1);
final long blobId2 = 347;
final File blobFile2 = mock(File.class);
@@ -156,7 +152,7 @@
final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2,
blobHandle2, false /* hasLeases */);
doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1);
- mUserBlobs.put(blobHandle2, blobMetadata2);
+ addBlob(blobHandle2, blobMetadata2);
final long blobId3 = 49875;
final File blobFile3 = mock(File.class);
@@ -165,7 +161,7 @@
final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3,
blobHandle3, true /* hasLeases */);
doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1);
- mUserBlobs.put(blobHandle3, blobMetadata3);
+ addBlob(blobHandle3, blobMetadata3);
mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
blobId1, blobId2, blobId3);
@@ -197,10 +193,10 @@
verify(blobMetadata2).destroy();
verify(blobMetadata3).destroy();
- assertThat(mUserBlobs.size()).isEqualTo(1);
- assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
- assertThat(mUserBlobs.get(blobHandle2)).isNull();
- assertThat(mUserBlobs.get(blobHandle3)).isNull();
+ assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
+ assertThat(mService.getBlobForTest(blobHandle1)).isNotNull();
+ assertThat(mService.getBlobForTest(blobHandle2)).isNull();
+ assertThat(mService.getBlobForTest(blobHandle3)).isNull();
assertThat(mService.getActiveIdsForTest()).containsExactly(
sessionId2, sessionId3, blobId1);
@@ -293,7 +289,7 @@
"label1", System.currentTimeMillis() - 2000, "tag1");
final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1,
true /* hasLeases */);
- mUserBlobs.put(blobHandle1, blobMetadata1);
+ addBlob(blobHandle1, blobMetadata1);
final long blobId2 = 78974;
final File blobFile2 = mock(File.class);
@@ -301,7 +297,7 @@
"label2", System.currentTimeMillis() + 30000, "tag2");
final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2,
true /* hasLeases */);
- mUserBlobs.put(blobHandle2, blobMetadata2);
+ addBlob(blobHandle2, blobMetadata2);
final long blobId3 = 97;
final File blobFile3 = mock(File.class);
@@ -309,7 +305,7 @@
"label3", System.currentTimeMillis() + 4400000, "tag3");
final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3,
false /* hasLeases */);
- mUserBlobs.put(blobHandle3, blobMetadata3);
+ addBlob(blobHandle3, blobMetadata3);
mService.addActiveIdsForTest(blobId1, blobId2, blobId3);
@@ -321,8 +317,8 @@
verify(blobMetadata2, never()).destroy();
verify(blobMetadata3).destroy();
- assertThat(mUserBlobs.size()).isEqualTo(1);
- assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
+ assertThat(mService.getBlobsCountForTest()).isEqualTo(1);
+ assertThat(mService.getBlobForTest(blobHandle2)).isNotNull();
assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2);
assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
@@ -336,21 +332,21 @@
doReturn(size1).when(blobMetadata1).getSize();
doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1);
doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2);
- mUserBlobs.put(mock(BlobHandle.class), blobMetadata1);
+ addBlob(mock(BlobHandle.class), blobMetadata1);
final BlobMetadata blobMetadata2 = mock(BlobMetadata.class);
final long size2 = 89475;
doReturn(size2).when(blobMetadata2).getSize();
doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1);
doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2);
- mUserBlobs.put(mock(BlobHandle.class), blobMetadata2);
+ addBlob(mock(BlobHandle.class), blobMetadata2);
final BlobMetadata blobMetadata3 = mock(BlobMetadata.class);
final long size3 = 328732;
doReturn(size3).when(blobMetadata3).getSize();
doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1);
doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2);
- mUserBlobs.put(mock(BlobHandle.class), blobMetadata3);
+ addBlob(mock(BlobHandle.class), blobMetadata3);
// Verify usage is calculated correctly
assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1))
@@ -388,6 +384,11 @@
return blobMetadata;
}
+ private void addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata) {
+ doReturn(blobHandle).when(blobMetadata).getBlobHandle();
+ mService.addBlobLocked(blobMetadata);
+ }
+
private class TestHandler extends Handler {
TestHandler(Looper looper) {
super(looper);
diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
index 5bef877..e9b5b62 100644
--- a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -184,6 +184,7 @@
false /* callerInstantApp */,
null /* resolvedType */,
null /* requiredPermissions */,
+ null /* excludedPermissions */,
0 /* appOp */,
null /* options */,
new ArrayList<>(receivers), // Make a copy to not affect the original list.
diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
deleted file mode 100644
index edc0d46..0000000
--- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * 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.app;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.Manifest;
-import android.app.GameManager;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-
-import java.util.HashMap;
-import java.util.function.Supplier;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@Presubmit
-public class GameManagerServiceTests {
-
- private static final String TAG = "GameServiceTests";
- private static final String PACKAGE_NAME_INVALID = "com.android.app";
- private static final int USER_ID_1 = 1001;
- private static final int USER_ID_2 = 1002;
-
- // Stolen from ConnectivityServiceTest.MockContext
- static class MockContext extends ContextWrapper {
- private static final String TAG = "MockContext";
-
- // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
- private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
-
- @Mock
- private final MockPackageManager mMockPackageManager;
-
- MockContext(Context base) {
- super(base);
- mMockPackageManager = new MockPackageManager();
- }
-
- /**
- * Mock checks for the specified permission, and have them behave as per {@code granted}.
- *
- * <p>Passing null reverts to default behavior, which does a real permission check on the
- * test package.
- *
- * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
- * {@link PackageManager#PERMISSION_DENIED}.
- */
- public void setPermission(String permission, Integer granted) {
- mMockedPermissions.put(permission, granted);
- }
-
- private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
- final Integer granted = mMockedPermissions.get(permission);
- return granted != null ? granted : ifAbsent.get();
- }
-
- @Override
- public int checkPermission(String permission, int pid, int uid) {
- return checkMockedPermission(
- permission, () -> super.checkPermission(permission, pid, uid));
- }
-
- @Override
- public int checkCallingOrSelfPermission(String permission) {
- return checkMockedPermission(
- permission, () -> super.checkCallingOrSelfPermission(permission));
- }
-
- @Override
- public void enforceCallingOrSelfPermission(String permission, String message) {
- final Integer granted = mMockedPermissions.get(permission);
- if (granted == null) {
- super.enforceCallingOrSelfPermission(permission, message);
- return;
- }
-
- if (!granted.equals(PackageManager.PERMISSION_GRANTED)) {
- throw new SecurityException("[Test] permission denied: " + permission);
- }
- }
-
- @Override
- public PackageManager getPackageManager() {
- return mMockPackageManager;
- }
- }
-
- @Mock
- private MockContext mMockContext;
-
- private String mPackageName;
-
- @Before
- public void setUp() throws Exception {
- mMockContext = new MockContext(InstrumentationRegistry.getContext());
- mPackageName = mMockContext.getPackageName();
- }
-
- private void mockModifyGameModeGranted() {
- mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
- PackageManager.PERMISSION_GRANTED);
- }
-
- private void mockModifyGameModeDenied() {
- mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
- PackageManager.PERMISSION_DENIED);
- }
-
- /**
- * By default game mode is not supported.
- */
- @Test
- public void testGameModeDefaultValue() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test the default behaviour for a nonexistent user.
- */
- @Test
- public void testDefaultValueForNonexistentUser() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_2);
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- }
-
- /**
- * Test getter and setter of game modes.
- */
- @Test
- public void testGameMode() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- mockModifyGameModeGranted();
-
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_1);
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test permission.MANAGE_GAME_MODE is checked
- */
- @Test
- public void testGetGameModeInvalidPackageName() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- try {
- assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
- gameManagerService.getGameMode(PACKAGE_NAME_INVALID,
- USER_ID_1));
-
- fail("GameManagerService failed to generate SecurityException when "
- + "permission.MANAGE_GAME_MODE is not granted.");
- } catch (SecurityException ignored) {
- }
-
- // The test should throw an exception, so the test is passing if we get here.
- }
-
- /**
- * Test permission.MANAGE_GAME_MODE is checked
- */
- @Test
- public void testSetGameModePermissionDenied() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
-
- // Update the game mode so we can read back something valid.
- mockModifyGameModeGranted();
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated.
- mockModifyGameModeDenied();
- try {
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_1);
-
- fail("GameManagerService failed to generate SecurityException when "
- + "permission.MANAGE_GAME_MODE is denied.");
- } catch (SecurityException ignored) {
- }
-
- // The test should throw an exception, so the test is passing if we get here.
- mockModifyGameModeGranted();
- // Verify that the Game Mode value wasn't updated.
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- }
-
- /**
- * Test game modes are user-specific.
- */
- @Test
- public void testGameModeMultipleUsers() {
- GameManagerService gameManagerService = new GameManagerService(mMockContext);
- gameManagerService.onUserStarting(USER_ID_1);
- gameManagerService.onUserStarting(USER_ID_2);
-
- mockModifyGameModeGranted();
-
- // Set User 1 to Standard
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Set User 2 to Performance and verify User 1 is still Standard
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE,
- USER_ID_2);
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- assertEquals(GameManager.GAME_MODE_STANDARD,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
-
- // Set User 1 to Battery and verify User 2 is still Performance
- gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY,
- USER_ID_1);
- assertEquals(GameManager.GAME_MODE_BATTERY,
- gameManagerService.getGameMode(mPackageName, USER_ID_1));
- assertEquals(GameManager.GAME_MODE_PERFORMANCE,
- gameManagerService.getGameMode(mPackageName, USER_ID_2));
- }
-}
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 4240581..b552fd5 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java
@@ -32,6 +32,8 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -203,7 +205,7 @@
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
// Set schema1
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
@@ -280,7 +282,7 @@
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
@@ -353,7 +355,7 @@
@Test
public void testSetSchema_defaultPlatformVisible() throws Exception {
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
@@ -372,7 +374,7 @@
@Test
public void testSetSchema_platformHidden() throws Exception {
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
@@ -391,7 +393,7 @@
@Test
public void testSetSchema_defaultNotPackageAccessible() throws Exception {
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
@@ -419,7 +421,7 @@
mMockPackageManager.mockGetPackageUidAsUser(packageNameFoo, mContext.getUserId(), uidFoo);
mMockPackageManager.mockAddSigningCertificate(packageNameFoo, sha256CertFoo);
- String prefix = AppSearchImpl.createPrefix("package", "database");
+ String prefix = PrefixUtil.createPrefix("package", "database");
mAppSearchImpl.setSchema(
"package",
"database",
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
index 8d35ebe..11ae76b 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/VisibilityStoreTest.java
@@ -28,6 +28,8 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
+
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -78,13 +80,9 @@
@Test
public void testValidPackageName() {
assertThat(VisibilityStore.PACKAGE_NAME)
- .doesNotContain(
- "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+ .doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
assertThat(VisibilityStore.PACKAGE_NAME)
- .doesNotContain(
- ""
- + AppSearchImpl
- .DATABASE_DELIMITER); // Convert the chars to CharSequences
+ .doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
}
/**
@@ -93,13 +91,9 @@
@Test
public void testValidDatabaseName() {
assertThat(VisibilityStore.DATABASE_NAME)
- .doesNotContain(
- "" + AppSearchImpl.PACKAGE_DELIMITER); // Convert the chars to CharSequences
+ .doesNotContain(String.valueOf(PrefixUtil.PACKAGE_DELIMITER));
assertThat(VisibilityStore.DATABASE_NAME)
- .doesNotContain(
- ""
- + AppSearchImpl
- .DATABASE_DELIMITER); // Convert the chars to CharSequences
+ .doesNotContain(String.valueOf(PrefixUtil.DATABASE_DELIMITER));
}
@Test
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 ba4d585..380d9be 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
@@ -16,6 +16,10 @@
package com.android.server.appsearch.external.localstorage;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix;
+import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument;
+
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.expectThrows;
@@ -35,8 +39,10 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.server.appsearch.external.localstorage.converter.GenericDocumentToProtoConverter;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.GetOptimizeInfoResultProto;
+import com.android.server.appsearch.proto.PersistType;
import com.android.server.appsearch.proto.PropertyConfigProto;
import com.android.server.appsearch.proto.PropertyProto;
import com.android.server.appsearch.proto.SchemaProto;
@@ -47,6 +53,7 @@
import com.android.server.appsearch.proto.TermMatchType;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.junit.Before;
@@ -54,6 +61,7 @@
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -96,56 +104,71 @@
// Create a copy so we can modify it.
List<SchemaTypeConfigProto> existingTypes =
new ArrayList<>(existingSchemaBuilder.getTypesList());
-
- SchemaProto newSchema =
- SchemaProto.newBuilder()
- .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build())
- .addTypes(
- SchemaTypeConfigProto.newBuilder()
- .setSchemaType("TestType")
- .addProperties(
- PropertyConfigProto.newBuilder()
- .setPropertyName("subject")
- .setDataType(
- PropertyConfigProto.DataType.Code
- .STRING)
- .setCardinality(
- PropertyConfigProto.Cardinality.Code
- .OPTIONAL)
- .setStringIndexingConfig(
- StringIndexingConfig.newBuilder()
- .setTokenizerType(
- StringIndexingConfig
- .TokenizerType
- .Code.PLAIN)
- .setTermMatchType(
- TermMatchType.Code
- .PREFIX)
- .build())
- .build())
- .addProperties(
- PropertyConfigProto.newBuilder()
- .setPropertyName("link")
- .setDataType(
- PropertyConfigProto.DataType.Code
- .DOCUMENT)
- .setCardinality(
- PropertyConfigProto.Cardinality.Code
- .OPTIONAL)
- .setSchemaType("RefType")
+ SchemaTypeConfigProto schemaTypeConfigProto1 =
+ SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build();
+ SchemaTypeConfigProto schemaTypeConfigProto2 =
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("TestType")
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setStringIndexingConfig(
+ StringIndexingConfig.newBuilder()
+ .setTokenizerType(
+ StringIndexingConfig.TokenizerType
+ .Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
.build())
.build())
+ .addProperties(
+ PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(
+ PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("RefType")
+ .build())
+ .build();
+ SchemaTypeConfigProto schemaTypeConfigProto3 =
+ SchemaTypeConfigProto.newBuilder().setSchemaType("RefType").build();
+ SchemaProto newSchema =
+ SchemaProto.newBuilder()
+ .addTypes(schemaTypeConfigProto1)
+ .addTypes(schemaTypeConfigProto2)
+ .addTypes(schemaTypeConfigProto3)
.build();
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
mAppSearchImpl.rewriteSchema(
- AppSearchImpl.createPrefix("package", "newDatabase"),
- existingSchemaBuilder,
- newSchema);
+ createPrefix("package", "newDatabase"), existingSchemaBuilder, newSchema);
// We rewrote all the new types that were added. And nothing was removed.
- assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
- .containsExactly("package$newDatabase/Foo", "package$newDatabase/TestType");
+ assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
+ .containsExactly(
+ "package$newDatabase/Foo",
+ "package$newDatabase/TestType",
+ "package$newDatabase/RefType");
+ assertThat(
+ rewrittenSchemaResults
+ .mRewrittenPrefixedTypes
+ .get("package$newDatabase/Foo")
+ .getSchemaType())
+ .isEqualTo("package$newDatabase/Foo");
+ assertThat(
+ rewrittenSchemaResults
+ .mRewrittenPrefixedTypes
+ .get("package$newDatabase/TestType")
+ .getSchemaType())
+ .isEqualTo("package$newDatabase/TestType");
+ assertThat(
+ rewrittenSchemaResults
+ .mRewrittenPrefixedTypes
+ .get("package$newDatabase/RefType")
+ .getSchemaType())
+ .isEqualTo("package$newDatabase/RefType");
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
SchemaProto expectedSchema =
@@ -190,6 +213,10 @@
"package$newDatabase/RefType")
.build())
.build())
+ .addTypes(
+ SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("package$newDatabase/RefType")
+ .build())
.build();
existingTypes.addAll(expectedSchema.getTypesList());
@@ -216,12 +243,12 @@
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
mAppSearchImpl.rewriteSchema(
- AppSearchImpl.createPrefix("package", "existingDatabase"),
+ createPrefix("package", "existingDatabase"),
existingSchemaBuilder,
newSchema);
// Nothing was removed, but the method did rewrite the type name.
- assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
+ assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet())
.containsExactly("package$existingDatabase/Foo");
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty();
@@ -251,14 +278,15 @@
AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults =
mAppSearchImpl.rewriteSchema(
- AppSearchImpl.createPrefix("package", "existingDatabase"),
+ createPrefix("package", "existingDatabase"),
existingSchemaBuilder,
newSchema);
// Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the
// new schema.
assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes)
- .containsExactly("package$existingDatabase/Bar");
+ .containsKey("package$existingDatabase/Bar");
+ assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet().size()).isEqualTo(1);
assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes)
.containsExactly("package$existingDatabase/Foo");
@@ -308,8 +336,7 @@
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
- mAppSearchImpl.addPrefixToDocument(
- actualDocument, AppSearchImpl.createPrefix("package", "databaseName"));
+ addPrefixToDocument(actualDocument, createPrefix("package", "databaseName"));
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
}
@@ -347,8 +374,7 @@
.build();
DocumentProto.Builder actualDocument = documentProto.toBuilder();
- assertThat(mAppSearchImpl.removePrefixesFromDocument(actualDocument))
- .isEqualTo("package$databaseName/");
+ assertThat(removePrefixesFromDocument(actualDocument)).isEqualTo("package$databaseName/");
assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto);
}
@@ -365,8 +391,7 @@
DocumentProto.Builder actualDocument = documentProto.toBuilder();
AppSearchException e =
expectThrows(
- AppSearchException.class,
- () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+ AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
}
@@ -391,8 +416,7 @@
DocumentProto.Builder actualDocument = documentProto.toBuilder();
AppSearchException e =
expectThrows(
- AppSearchException.class,
- () -> mAppSearchImpl.removePrefixesFromDocument(actualDocument));
+ AppSearchException.class, () -> removePrefixesFromDocument(actualDocument));
assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names");
}
@@ -484,7 +508,7 @@
// Rewrite SearchSpec
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
- Collections.singleton(AppSearchImpl.createPrefix("package", "database")),
+ Collections.singleton(createPrefix("package", "database")),
ImmutableSet.of("package$database/type"));
assertThat(searchSpecProto.getSchemaTypeFiltersList())
.containsExactly("package$database/type");
@@ -531,8 +555,7 @@
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
ImmutableSet.of(
- AppSearchImpl.createPrefix("package", "database1"),
- AppSearchImpl.createPrefix("package", "database2")),
+ createPrefix("package", "database1"), createPrefix("package", "database2")),
ImmutableSet.of(
"package$database1/typeA", "package$database1/typeB",
"package$database2/typeA", "package$database2/typeB"));
@@ -573,8 +596,7 @@
assertThat(
mAppSearchImpl.rewriteSearchSpecForPrefixesLocked(
searchSpecProto,
- Collections.singleton(
- AppSearchImpl.createPrefix("package", "database")),
+ Collections.singleton(createPrefix("package", "database")),
/*allowedPrefixedSchemas=*/ Collections.emptySet()))
.isFalse();
}
@@ -1082,7 +1104,7 @@
// Has database1
Set<String> expectedPrefixes = new ArraySet<>(existingPrefixes);
- expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database1"));
+ expectedPrefixes.add(createPrefix("package", "database1"));
mAppSearchImpl.setSchema(
"package",
"database1",
@@ -1094,7 +1116,7 @@
assertThat(mAppSearchImpl.getPrefixesLocked()).containsExactlyElementsIn(expectedPrefixes);
// Has both databases
- expectedPrefixes.add(AppSearchImpl.createPrefix("package", "database2"));
+ expectedPrefixes.add(createPrefix("package", "database2"));
mAppSearchImpl.setSchema(
"package",
"database2",
@@ -1110,9 +1132,9 @@
public void testRewriteSearchResultProto() throws Exception {
final String prefix =
"com.package.foo"
- + AppSearchImpl.PACKAGE_DELIMITER
+ + PrefixUtil.PACKAGE_DELIMITER
+ "databaseName"
- + AppSearchImpl.DATABASE_DELIMITER;
+ + PrefixUtil.DATABASE_DELIMITER;
final String uri = "uri";
final String namespace = prefix + "namespace";
final String schemaType = prefix + "schema";
@@ -1128,18 +1150,22 @@
SearchResultProto.ResultProto.newBuilder().setDocument(documentProto).build();
SearchResultProto searchResultProto =
SearchResultProto.newBuilder().addResults(resultProto).build();
+ SchemaTypeConfigProto schemaTypeConfigProto =
+ SchemaTypeConfigProto.newBuilder().setSchemaType(schemaType).build();
+ Map<String, Map<String, SchemaTypeConfigProto>> schemaMap =
+ ImmutableMap.of(prefix, ImmutableMap.of(schemaType, schemaTypeConfigProto));
DocumentProto.Builder strippedDocumentProto = documentProto.toBuilder();
- AppSearchImpl.removePrefixesFromDocument(strippedDocumentProto);
+ removePrefixesFromDocument(strippedDocumentProto);
SearchResultPage searchResultPage =
- AppSearchImpl.rewriteSearchResultProto(searchResultProto);
+ AppSearchImpl.rewriteSearchResultProto(searchResultProto, schemaMap);
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getPackageName()).isEqualTo("com.package.foo");
assertThat(result.getDatabaseName()).isEqualTo("databaseName");
assertThat(result.getGenericDocument())
.isEqualTo(
GenericDocumentToProtoConverter.toGenericDocument(
- strippedDocumentProto.build()));
+ strippedDocumentProto.build(), prefix, schemaMap.get(prefix)));
}
}
@@ -1609,7 +1635,221 @@
expectThrows(
IllegalStateException.class,
() -> {
- appSearchImpl.persistToDisk();
+ appSearchImpl.persistToDisk(PersistType.Code.FULL);
});
}
+
+ @Test
+ public void testPutPersistsWithLiteFlush() throws Exception {
+ // Setup the index
+ Context context = ApplicationProvider.getApplicationContext();
+ File appsearchDir = mTemporaryFolder.newFolder();
+ AppSearchImpl appSearchImpl =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ appSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
+
+ // Add a document and persist it.
+ GenericDocument document =
+ new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+ appSearchImpl.putDocument("package", "database", document, /*logger=*/ null);
+ appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+ GenericDocument getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace1", "uri1", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document);
+
+ // That document should be visible even from another instance.
+ AppSearchImpl appSearchImpl2 =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+ getResult =
+ appSearchImpl2.getDocument(
+ "package", "database", "namespace1", "uri1", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document);
+ }
+
+ @Test
+ public void testDeletePersistsWithLiteFlush() throws Exception {
+ // Setup the index
+ Context context = ApplicationProvider.getApplicationContext();
+ File appsearchDir = mTemporaryFolder.newFolder();
+ AppSearchImpl appSearchImpl =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ appSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
+
+ // Add two documents and persist them.
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+ appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("namespace1", "uri2", "type").build();
+ appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+ appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+ GenericDocument getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace1", "uri1", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document1);
+ getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace1", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Delete the first document
+ appSearchImpl.remove("package", "database", "namespace1", "uri1");
+ appSearchImpl.persistToDisk(PersistType.Code.LITE);
+ expectThrows(
+ AppSearchException.class,
+ () ->
+ appSearchImpl.getDocument(
+ "package",
+ "database",
+ "namespace1",
+ "uri1",
+ Collections.emptyMap()));
+ getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace1", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Only the second document should be retrievable from another instance.
+ AppSearchImpl appSearchImpl2 =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+ expectThrows(
+ AppSearchException.class,
+ () ->
+ appSearchImpl2.getDocument(
+ "package",
+ "database",
+ "namespace1",
+ "uri1",
+ Collections.emptyMap()));
+ getResult =
+ appSearchImpl2.getDocument(
+ "package", "database", "namespace1", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+ }
+
+ @Test
+ public void testDeleteByQueryPersistsWithLiteFlush() throws Exception {
+ // Setup the index
+ Context context = ApplicationProvider.getApplicationContext();
+ File appsearchDir = mTemporaryFolder.newFolder();
+ AppSearchImpl appSearchImpl =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+
+ List<AppSearchSchema> schemas =
+ Collections.singletonList(new AppSearchSchema.Builder("type").build());
+ appSearchImpl.setSchema(
+ "package",
+ "database",
+ schemas,
+ /*schemasNotPlatformSurfaceable=*/ Collections.emptyList(),
+ /*schemasPackageAccessible=*/ Collections.emptyMap(),
+ /*forceOverride=*/ false,
+ /*version=*/ 0);
+
+ // Add two documents and persist them.
+ GenericDocument document1 =
+ new GenericDocument.Builder<>("namespace1", "uri1", "type").build();
+ appSearchImpl.putDocument("package", "database", document1, /*logger=*/ null);
+ GenericDocument document2 =
+ new GenericDocument.Builder<>("namespace2", "uri2", "type").build();
+ appSearchImpl.putDocument("package", "database", document2, /*logger=*/ null);
+ appSearchImpl.persistToDisk(PersistType.Code.LITE);
+
+ GenericDocument getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace1", "uri1", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document1);
+ getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace2", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Delete the first document
+ appSearchImpl.removeByQuery(
+ "package",
+ "database",
+ "",
+ new SearchSpec.Builder()
+ .addFilterNamespaces("namespace1")
+ .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+ .build());
+ appSearchImpl.persistToDisk(PersistType.Code.LITE);
+ expectThrows(
+ AppSearchException.class,
+ () ->
+ appSearchImpl.getDocument(
+ "package",
+ "database",
+ "namespace1",
+ "uri1",
+ Collections.emptyMap()));
+ getResult =
+ appSearchImpl.getDocument(
+ "package", "database", "namespace2", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+
+ // Only the second document should be retrievable from another instance.
+ AppSearchImpl appSearchImpl2 =
+ AppSearchImpl.create(
+ appsearchDir,
+ context,
+ VisibilityStore.NO_OP_USER_ID,
+ /*globalQuerierPackage=*/ "");
+ expectThrows(
+ AppSearchException.class,
+ () ->
+ appSearchImpl2.getDocument(
+ "package",
+ "database",
+ "namespace1",
+ "uri1",
+ Collections.emptyMap()));
+ getResult =
+ appSearchImpl2.getDocument(
+ "package", "database", "namespace2", "uri2", Collections.emptyMap());
+ assertThat(getResult).isEqualTo(document2);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
index 70e1e05..63f031722 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/GenericDocumentToProtoConverterTest.java
@@ -21,35 +21,50 @@
import android.app.appsearch.GenericDocument;
import com.android.server.appsearch.proto.DocumentProto;
+import com.android.server.appsearch.proto.PropertyConfigProto;
import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
import com.android.server.appsearch.protobuf.ByteString;
+import com.google.common.collect.ImmutableMap;
+
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class GenericDocumentToProtoConverterTest {
private static final byte[] BYTE_ARRAY_1 = new byte[] {(byte) 1, (byte) 2, (byte) 3};
private static final byte[] BYTE_ARRAY_2 = new byte[] {(byte) 4, (byte) 5, (byte) 6, (byte) 7};
+ private static final String SCHEMA_TYPE_1 = "sDocumentPropertiesSchemaType1";
+ private static final String SCHEMA_TYPE_2 = "sDocumentPropertiesSchemaType2";
private static final GenericDocument DOCUMENT_PROPERTIES_1 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "namespace", "sDocumentProperties1", "sDocumentPropertiesSchemaType1")
+ "namespace", "sDocumentProperties1", SCHEMA_TYPE_1)
.setCreationTimestampMillis(12345L)
.build();
private static final GenericDocument DOCUMENT_PROPERTIES_2 =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "namespace", "sDocumentProperties2", "sDocumentPropertiesSchemaType2")
+ "namespace", "sDocumentProperties2", SCHEMA_TYPE_2)
.setCreationTimestampMillis(6789L)
.build();
+ private static final SchemaTypeConfigProto SCHEMA_PROTO_1 =
+ SchemaTypeConfigProto.newBuilder().setSchemaType(SCHEMA_TYPE_1).build();
+ private static final SchemaTypeConfigProto SCHEMA_PROTO_2 =
+ SchemaTypeConfigProto.newBuilder().setSchemaType(SCHEMA_TYPE_2).build();
+ private static final String PREFIX = "package$databaseName/";
+ private static final Map<String, SchemaTypeConfigProto> SCHEMA_MAP =
+ ImmutableMap.of(
+ PREFIX + SCHEMA_TYPE_1, SCHEMA_PROTO_1, PREFIX + SCHEMA_TYPE_2, SCHEMA_PROTO_2);
@Test
public void testDocumentProtoConvert() {
GenericDocument document =
new GenericDocument.Builder<GenericDocument.Builder<?>>(
- "namespace", "uri1", "schemaType1")
+ "namespace", "uri1", SCHEMA_TYPE_1)
.setCreationTimestampMillis(5L)
.setScore(1)
.setTtlMillis(1L)
@@ -66,7 +81,7 @@
DocumentProto.Builder documentProtoBuilder =
DocumentProto.newBuilder()
.setUri("uri1")
- .setSchema("schemaType1")
+ .setSchema(SCHEMA_TYPE_1)
.setCreationTimestampMs(5L)
.setScore(1)
.setTtlMs(1L)
@@ -109,9 +124,133 @@
documentProtoBuilder.addProperties(propertyProtoMap.get(key));
}
DocumentProto documentProto = documentProtoBuilder.build();
- assertThat(GenericDocumentToProtoConverter.toDocumentProto(document))
- .isEqualTo(documentProto);
- assertThat(document)
- .isEqualTo(GenericDocumentToProtoConverter.toGenericDocument(documentProto));
+
+ GenericDocument convertedGenericDocument =
+ GenericDocumentToProtoConverter.toGenericDocument(
+ documentProto, PREFIX, SCHEMA_MAP);
+ DocumentProto convertedDocumentProto =
+ GenericDocumentToProtoConverter.toDocumentProto(document);
+
+ assertThat(convertedDocumentProto).isEqualTo(documentProto);
+ assertThat(convertedGenericDocument).isEqualTo(document);
+ }
+
+ @Test
+ public void testConvertDocument_whenPropertyHasEmptyList() {
+ String emptyStringPropertyName = "emptyStringProperty";
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri1")
+ .setSchema(SCHEMA_TYPE_1)
+ .setCreationTimestampMs(5L)
+ .setNamespace("namespace")
+ .addProperties(
+ PropertyProto.newBuilder().setName(emptyStringPropertyName).build())
+ .build();
+
+ PropertyConfigProto emptyStringListProperty =
+ PropertyConfigProto.newBuilder()
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setPropertyName(emptyStringPropertyName)
+ .build();
+ SchemaTypeConfigProto schemaTypeConfigProto =
+ SchemaTypeConfigProto.newBuilder()
+ .addProperties(emptyStringListProperty)
+ .setSchemaType(SCHEMA_TYPE_1)
+ .build();
+ Map<String, SchemaTypeConfigProto> schemaMap =
+ ImmutableMap.of(PREFIX + SCHEMA_TYPE_1, schemaTypeConfigProto);
+
+ GenericDocument convertedDocument =
+ GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX, schemaMap);
+
+ GenericDocument expectedDocument =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>(
+ "namespace", "uri1", SCHEMA_TYPE_1)
+ .setCreationTimestampMillis(5L)
+ .setPropertyString(emptyStringPropertyName)
+ .build();
+ assertThat(convertedDocument).isEqualTo(expectedDocument);
+ assertThat(expectedDocument.getPropertyStringArray(emptyStringPropertyName)).isEmpty();
+ }
+
+ @Test
+ public void testConvertDocument_whenNestedDocumentPropertyHasEmptyList() {
+ String emptyStringPropertyName = "emptyStringProperty";
+ String documentPropertyName = "documentProperty";
+ DocumentProto nestedDocumentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri2")
+ .setSchema(SCHEMA_TYPE_2)
+ .setCreationTimestampMs(5L)
+ .setNamespace("namespace")
+ .addProperties(
+ PropertyProto.newBuilder().setName(emptyStringPropertyName).build())
+ .build();
+ DocumentProto documentProto =
+ DocumentProto.newBuilder()
+ .setUri("uri1")
+ .setSchema(SCHEMA_TYPE_1)
+ .setCreationTimestampMs(5L)
+ .setNamespace("namespace")
+ .addProperties(
+ PropertyProto.newBuilder()
+ .addDocumentValues(nestedDocumentProto)
+ .setName(documentPropertyName)
+ .build())
+ .build();
+
+ PropertyConfigProto documentProperty =
+ PropertyConfigProto.newBuilder()
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setPropertyName(documentPropertyName)
+ .setSchemaType(SCHEMA_TYPE_2)
+ .build();
+ SchemaTypeConfigProto schemaTypeConfigProto =
+ SchemaTypeConfigProto.newBuilder()
+ .addProperties(documentProperty)
+ .setSchemaType(SCHEMA_TYPE_1)
+ .build();
+ PropertyConfigProto emptyStringListProperty =
+ PropertyConfigProto.newBuilder()
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setPropertyName(emptyStringPropertyName)
+ .build();
+ SchemaTypeConfigProto nestedSchemaTypeConfigProto =
+ SchemaTypeConfigProto.newBuilder()
+ .addProperties(emptyStringListProperty)
+ .setSchemaType(SCHEMA_TYPE_2)
+ .build();
+ Map<String, SchemaTypeConfigProto> schemaMap =
+ ImmutableMap.of(
+ PREFIX + SCHEMA_TYPE_1,
+ schemaTypeConfigProto,
+ PREFIX + SCHEMA_TYPE_2,
+ nestedSchemaTypeConfigProto);
+
+ GenericDocument convertedDocument =
+ GenericDocumentToProtoConverter.toGenericDocument(documentProto, PREFIX, schemaMap);
+
+ GenericDocument expectedDocument =
+ new GenericDocument.Builder<GenericDocument.Builder<?>>(
+ "namespace", "uri1", SCHEMA_TYPE_1)
+ .setCreationTimestampMillis(5L)
+ .setPropertyDocument(
+ documentPropertyName,
+ new GenericDocument.Builder<GenericDocument.Builder<?>>(
+ "namespace", "uri2", SCHEMA_TYPE_2)
+ .setCreationTimestampMillis(5L)
+ .setPropertyString(emptyStringPropertyName)
+ .build())
+ .build();
+ assertThat(convertedDocument).isEqualTo(expectedDocument);
+ assertThat(
+ expectedDocument
+ .getPropertyDocument(documentPropertyName)
+ .getPropertyStringArray(emptyStringPropertyName))
+ .isEmpty();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
index d07211f..26fac49 100644
--- a/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
+++ b/services/tests/servicestests/src/com/android/server/appsearch/external/localstorage/converter/SnippetTest.java
@@ -21,8 +21,10 @@
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResultPage;
+import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
import com.android.server.appsearch.proto.DocumentProto;
import com.android.server.appsearch.proto.PropertyProto;
+import com.android.server.appsearch.proto.SchemaTypeConfigProto;
import com.android.server.appsearch.proto.SearchResultProto;
import com.android.server.appsearch.proto.SnippetMatchProto;
import com.android.server.appsearch.proto.SnippetProto;
@@ -30,20 +32,29 @@
import org.junit.Test;
import java.util.Collections;
+import java.util.Map;
public class SnippetTest {
+ private static final String SCHEMA_TYPE = "schema1";
+ private static final String PACKAGE_NAME = "packageName";
+ private static final String DATABASE_NAME = "databaseName";
+ private static final String PREFIX = PrefixUtil.createPrefix(PACKAGE_NAME, DATABASE_NAME);
+ private static final SchemaTypeConfigProto SCHEMA_TYPE_CONFIG_PROTO =
+ SchemaTypeConfigProto.newBuilder().setSchemaType(PREFIX + SCHEMA_TYPE).build();
+ private static final Map<String, Map<String, SchemaTypeConfigProto>> SCHEMA_MAP =
+ Collections.singletonMap(
+ PREFIX,
+ Collections.singletonMap(PREFIX + SCHEMA_TYPE, SCHEMA_TYPE_CONFIG_PROTO));
// TODO(tytytyww): Add tests for Double and Long Snippets.
@Test
public void testSingleStringSnippet() {
-
final String propertyKeyString = "content";
final String propertyValueString =
"A commonly used fake word is foo.\n"
+ " Another nonsense word that’s used a lot\n"
+ " is bar.\n";
final String uri = "uri1";
- final String schemaType = "schema1";
final String searchWord = "foo";
final String exactMatch = "foo";
final String window = "is foo";
@@ -57,7 +68,7 @@
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri(uri)
- .setSchema(schemaType)
+ .setSchema(SCHEMA_TYPE)
.addProperties(property)
.build();
SnippetProto snippetProto =
@@ -86,8 +97,9 @@
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
searchResultProto,
- Collections.singletonList("packageName"),
- Collections.singletonList("databaseName"));
+ Collections.singletonList(PACKAGE_NAME),
+ Collections.singletonList(DATABASE_NAME),
+ SCHEMA_MAP);
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match = result.getMatches().get(0);
assertThat(match.getPropertyPath()).isEqualTo(propertyKeyString);
@@ -112,7 +124,6 @@
+ " Another nonsense word that’s used a lot\n"
+ " is bar.\n";
final String uri = "uri1";
- final String schemaType = "schema1";
final String searchWord = "foo";
final String exactMatch = "foo";
final String window = "is foo";
@@ -126,7 +137,7 @@
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri(uri)
- .setSchema(schemaType)
+ .setSchema(SCHEMA_TYPE)
.addProperties(property)
.build();
SearchResultProto.ResultProto resultProto =
@@ -137,8 +148,9 @@
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
searchResultProto,
- Collections.singletonList("packageName"),
- Collections.singletonList("databaseName"));
+ Collections.singletonList(PACKAGE_NAME),
+ Collections.singletonList(DATABASE_NAME),
+ SCHEMA_MAP);
for (SearchResult result : searchResultPage.getResults()) {
assertThat(result.getMatches()).isEmpty();
}
@@ -162,7 +174,7 @@
DocumentProto documentProto =
DocumentProto.newBuilder()
.setUri("uri1")
- .setSchema("schema1")
+ .setSchema(SCHEMA_TYPE)
.addProperties(property1)
.addProperties(property2)
.build();
@@ -203,8 +215,9 @@
SearchResultPage searchResultPage =
SearchResultToProtoConverter.toSearchResultPage(
searchResultProto,
- Collections.singletonList("packageName"),
- Collections.singletonList("databaseName"));
+ Collections.singletonList(PACKAGE_NAME),
+ Collections.singletonList(DATABASE_NAME),
+ SCHEMA_MAP);
for (SearchResult result : searchResultPage.getResults()) {
SearchResult.MatchInfo match1 = result.getMatches().get(0);
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 79a5ed6..5c53d43 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -98,8 +98,9 @@
Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
- mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+ mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
any(BluetoothDevice.class),
@@ -209,20 +210,23 @@
((NoOpAudioSystemAdapter) mSpyAudioSystem).configureIsStreamActive(mockMediaPlayback);
// first connection: ensure the device is connected as a starting condition for the test
- mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1);
+ mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
// disconnection
- mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
- BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1);
+ mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+ BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1));
if (delayAfterDisconnection > 0) {
Thread.sleep(delayAfterDisconnection);
}
// reconnection
- mAudioDeviceBroker.postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(mFakeBtDevice,
- BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2);
+ mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
+ new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
+ BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2));
Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
// Verify disconnection has been cancelled and we're seeing two connections attempts,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index e322ce5..96bab61 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -308,6 +308,8 @@
componentInfo,
type,
false /* resetLockoutRequiresHardwareAuthToken */));
+
+ when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
}
private void setupFace(int id, boolean confirmationAlwaysRequired,
@@ -329,6 +331,6 @@
}
});
- when(mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a5fbab5..ec3bea3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -378,7 +378,7 @@
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
// Disabled in user settings receives onError
- when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
null /* authenticators */);
waitForIdle();
@@ -389,7 +389,7 @@
// Enrolled, not disabled in settings, user requires confirmation in settings
resetReceivers();
- when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
when(mBiometricService.mSettingObserver.getConfirmationAlwaysRequired(
anyInt() /* modality */, anyInt() /* userId */))
.thenReturn(true);
@@ -1197,7 +1197,7 @@
@Test
public void testCanAuthenticate_whenBiometricsNotEnabledForApps() throws Exception {
setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
- when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(false);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
// When only biometric is requested
@@ -1322,6 +1322,8 @@
final int testId = 0;
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
+
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
@@ -1536,7 +1538,7 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
@@ -1564,7 +1566,7 @@
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- when(mBiometricService.mSettingObserver.getFaceEnabledForApps(anyInt())).thenReturn(true);
+ when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
assertEquals(modalities.length, strengths.length);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 73ec5b8..89798ce 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
-import static android.app.admin.PasswordMetrics.computeForPassword;
+import static android.app.admin.PasswordMetrics.computeForPasswordOrPin;
import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
import static android.net.InetAddresses.parseNumericAddress;
@@ -1551,6 +1551,16 @@
@Test
public void testSetProfileOwner_failures() throws Exception {
// TODO Test more failure cases. Basically test all chacks in enforceCanSetProfileOwner().
+ // Package doesn't exist and caller is not system
+ assertExpectException(SecurityException.class,
+ /* messageRegex= */ "Calling identity is not authorized",
+ () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
+
+ // Package exists, but caller is not system
+ setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_SYSTEM_USER_UID);
+ assertExpectException(SecurityException.class,
+ /* messageRegex= */ "Calling identity is not authorized",
+ () -> dpm.setProfileOwner(admin1, "owner-name", UserHandle.USER_SYSTEM));
}
@Test
@@ -5156,7 +5166,8 @@
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
+ PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+ "abcdXYZ5".getBytes(), /* isPin */ false);
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5183,7 +5194,8 @@
reset(mContext.spiedContext);
assertThat(dpm.isActivePasswordSufficient()).isFalse();
- PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
+ PasswordMetrics passwordMetricsWithSymbols = computeForPasswordOrPin(
+ "abcd.XY5".getBytes(), /* isPin */ false);
setActivePasswordState(passwordMetricsWithSymbols);
assertThat(dpm.isActivePasswordSufficient()).isTrue();
@@ -5237,7 +5249,7 @@
parentDpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_MEDIUM);
when(getServices().lockSettingsInternal.getUserPasswordMetrics(UserHandle.USER_SYSTEM))
- .thenReturn(computeForPassword("184342".getBytes()));
+ .thenReturn(computeForPasswordOrPin("184342".getBytes(), /* isPin */ true));
// Numeric password is compliant with current requirement (QUALITY_NUMERIC set explicitly
// on the parent admin)
@@ -6360,7 +6372,7 @@
.thenReturn(CALLER_USER_HANDLE);
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(CALLER_USER_HANDLE))
- .thenReturn(computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_MEDIUM);
}
@@ -6380,10 +6392,10 @@
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(CALLER_USER_HANDLE))
- .thenReturn(computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPasswordOrPin("asdf".getBytes(), /* isPin */ false));
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(parentUser.id))
- .thenReturn(computeForPassword("parentUser".getBytes()));
+ .thenReturn(computeForPasswordOrPin("parentUser".getBytes(), /* isPin */ false));
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
}
@@ -7059,13 +7071,15 @@
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_NONE);
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = computeForPassword("1234".getBytes());
+ PasswordMetrics passwordMetricsNoSymbols = computeForPasswordOrPin(
+ "1234".getBytes(), /* isPin */ true);
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_LOW);
assertThat(dpm.isActivePasswordSufficient()).isFalse();
reset(mContext.spiedContext);
- passwordMetricsNoSymbols = computeForPassword("84125312943a".getBytes());
+ passwordMetricsNoSymbols = computeForPasswordOrPin(
+ "84125312943a".getBytes(), /* isPin */ false);
setActivePasswordState(passwordMetricsNoSymbols);
assertThat(dpm.getPasswordComplexity()).isEqualTo(PASSWORD_COMPLEXITY_HIGH);
// using isActivePasswordSufficient
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index d888b92..876c845 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -37,7 +37,7 @@
@JvmStatic
@Parameterized.Parameters(name = "deferRebuild {0}")
- fun parameters() = arrayOf(true, false)
+ fun parameters() = arrayOf(/*true, */false)
}
private lateinit var mapper: OverlayReferenceMapper
@@ -55,11 +55,17 @@
fun targetWithOverlay() {
val target = mockTarget()
val overlay = mockOverlay()
- val existing = mapper.addInOrder(overlay)
+ val existing = mapper.addInOrder(overlay) {
+ assertThat(it).isEmpty()
+ }
assertEmpty()
- mapper.addInOrder(target, existing = existing)
+ mapper.addInOrder(target, existing = existing) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
- mapper.remove(target)
+ mapper.remove(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertEmpty()
}
@@ -78,22 +84,34 @@
)
)
)
- val existing = mapper.addInOrder(overlay0, overlay1)
+ val existing = mapper.addInOrder(overlay0, overlay1) {
+ assertThat(it).isEmpty()
+ }
assertEmpty()
- mapper.addInOrder(target, existing = existing)
+ mapper.addInOrder(target, existing = existing) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay0, overlay1))
- mapper.remove(overlay0)
+ mapper.remove(overlay0) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay1))
- mapper.remove(target)
+ mapper.remove(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertEmpty()
}
@Test
fun targetWithoutOverlay() {
val target = mockTarget()
- mapper.addInOrder(target)
+ mapper.addInOrder(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
- mapper.remove(target)
+ mapper.remove(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertEmpty()
}
@@ -101,11 +119,17 @@
fun overlayWithTarget() {
val target = mockTarget()
val overlay = mockOverlay()
- val existing = mapper.addInOrder(target)
+ val existing = mapper.addInOrder(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
- mapper.addInOrder(overlay, existing = existing)
+ mapper.addInOrder(overlay, existing = existing) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target, overlay))
- mapper.remove(overlay)
+ mapper.remove(overlay) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
}
@@ -122,34 +146,52 @@
)
)
)
- mapper.addInOrder(target0, target1, overlay)
+ mapper.addInOrder(target0, target1, overlay) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
- mapper.remove(target0)
+ mapper.remove(target0) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
- mapper.remove(target1)
+ mapper.remove(target1) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
assertEmpty()
}
@Test
fun overlayWithoutTarget() {
val overlay = mockOverlay()
- mapper.addInOrder(overlay)
+ mapper.addInOrder(overlay) {
+ assertThat(it).isEmpty()
+ }
// An overlay can only have visibility exposed through its target
assertEmpty()
- mapper.remove(overlay)
+ mapper.remove(overlay) {
+ assertThat(it).isEmpty()
+ }
assertEmpty()
}
private fun OverlayReferenceMapper.addInOrder(
vararg pkgs: AndroidPackage,
- existing: MutableMap<String, AndroidPackage> = mutableMapOf()
- ) = pkgs.fold(existing) { map, pkg ->
- addPkg(pkg, map)
- map[pkg.packageName] = pkg
- return@fold map
+ existing: MutableMap<String, AndroidPackage> = mutableMapOf(),
+ assertion: (changedPackages: Set<String>) -> Unit
+ ): MutableMap<String, AndroidPackage> {
+ val changedPackages = mutableSetOf<String>()
+ pkgs.forEach {
+ changedPackages += addPkg(it, existing)
+ existing[it.packageName] = it
+ }
+ assertion(changedPackages)
+ return existing
}
- private fun OverlayReferenceMapper.remove(pkg: AndroidPackage) = removePkg(pkg.packageName)
+ private fun OverlayReferenceMapper.remove(
+ pkg: AndroidPackage,
+ assertion: (changedPackages: Set<String>) -> Unit
+ ) = assertion(removePkg(pkg.packageName))
private fun assertMapping(vararg pairs: Pair<String, Set<AndroidPackage>>) {
val expected = pairs.associate { it }
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 8070bd1..558e259 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -7,6 +7,14 @@
"include-filter": "com.android.server.om."
}
]
+ },
+ {
+ "name": "PackageManagerServiceHostTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.pm.test.OverlayActorVisibilityTest"
+ }
+ ]
}
]
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 9f428c7..67dd055 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -799,10 +799,18 @@
simulateAddBasicAndroid(appsFilter);
appsFilter.onSystemReady();
- PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
+ // Packages must be added in actor -> overlay -> target order so that the implicit
+ // visibility of the actor into the overlay can be tested
+
+ PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
PackageSetting overlaySetting =
simulateAddPackage(appsFilter, overlay, DUMMY_OVERLAY_APPID);
- PackageSetting actorSetting = simulateAddPackage(appsFilter, actor, DUMMY_ACTOR_APPID);
+
+ // Actor can not see overlay (yet)
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ overlaySetting, SYSTEM_USER));
+
+ PackageSetting targetSetting = simulateAddPackage(appsFilter, target, DUMMY_TARGET_APPID);
// Actor can see both target and overlay
assertFalse(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
@@ -821,6 +829,12 @@
actorSetting, SYSTEM_USER));
assertTrue(appsFilter.shouldFilterApplication(DUMMY_OVERLAY_APPID, overlaySetting,
actorSetting, SYSTEM_USER));
+
+ appsFilter.removePackage(targetSetting);
+
+ // Actor loses visibility to the overlay via removal of the target
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_ACTOR_APPID, actorSetting,
+ overlaySetting, SYSTEM_USER));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 742f503..32fed3b 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -248,21 +248,6 @@
mStubbedTimeDetectorStrategy.verifyDumpCalled();
}
- @Test
- public void testAutoTimeDetectionToggle() throws Exception {
- mTimeDetectorService.handleAutoTimeDetectionChanged();
- mTestHandler.assertTotalMessagesEnqueued(1);
- mTestHandler.waitForMessagesToBeProcessed();
- mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
-
- mStubbedTimeDetectorStrategy.resetCallTracking();
-
- mTimeDetectorService.handleAutoTimeDetectionChanged();
- mTestHandler.assertTotalMessagesEnqueued(2);
- mTestHandler.waitForMessagesToBeProcessed();
- mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
- }
-
private static TelephonyTimeSuggestion createTelephonyTimeSuggestion() {
int slotIndex = 1234;
TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
@@ -298,7 +283,6 @@
private NetworkTimeSuggestion mLastNetworkSuggestion;
private GnssTimeSuggestion mLastGnssSuggestion;
private ExternalTimeSuggestion mLastExternalSuggestion;
- private boolean mHandleAutoTimeDetectionChangedCalled;
private boolean mDumpCalled;
@Override
@@ -333,11 +317,6 @@
}
@Override
- public void handleAutoTimeConfigChanged() {
- mHandleAutoTimeDetectionChangedCalled = true;
- }
-
- @Override
public void dump(IndentingPrintWriter pw, String[] args) {
mDumpCalled = true;
}
@@ -348,7 +327,6 @@
mLastNetworkSuggestion = null;
mLastGnssSuggestion = null;
mLastExternalSuggestion = null;
- mHandleAutoTimeDetectionChangedCalled = false;
mDumpCalled = false;
}
@@ -372,10 +350,6 @@
assertEquals(expectedSuggestion, mLastExternalSuggestion);
}
- void verifyHandleAutoTimeDetectionChangedCalled() {
- assertTrue(mHandleAutoTimeDetectionChangedCalled);
- }
-
void verifyDumpCalled() {
assertTrue(mDumpCalled);
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 095703e..0d5b5a5 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -37,6 +37,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.timedetector.TimeDetectorStrategy.Origin;
+import com.android.server.timezonedetector.ConfigurationChangeListener;
import org.junit.Before;
import org.junit.Test;
@@ -46,6 +47,7 @@
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
+import java.util.Objects;
@RunWith(AndroidJUnit4.class)
public class TimeDetectorStrategyImplTest {
@@ -1133,11 +1135,17 @@
private long mSystemClockMillis;
private int mSystemClockUpdateThresholdMillis = 2000;
private int[] mAutoOriginPriorities = PROVIDERS_PRIORITY;
+ private ConfigurationChangeListener mConfigChangeListener;
// Tracking operations.
private boolean mSystemClockWasSet;
@Override
+ public void setConfigChangeListener(ConfigurationChangeListener listener) {
+ mConfigChangeListener = Objects.requireNonNull(listener);
+ }
+
+ @Override
public int systemClockUpdateThresholdMillis() {
return mSystemClockUpdateThresholdMillis;
}
@@ -1230,6 +1238,7 @@
void simulateAutoTimeZoneDetectionToggle() {
mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled;
+ mConfigChangeListener.onChange();
}
void verifySystemClockNotSet() {
@@ -1330,7 +1339,6 @@
Script simulateAutoTimeDetectionToggle() {
mFakeEnvironment.simulateAutoTimeZoneDetectionToggle();
- mTimeDetectorStrategy.handleAutoTimeConfigChanged();
return this;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 809b6d5..182848b4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -123,6 +123,7 @@
for (long i = cal.getTimeInMillis(); i >= 5; i--) {
File file = mock(File.class);
when(file.getName()).thenReturn(String.valueOf(i));
+ when(file.getAbsolutePath()).thenReturn(String.valueOf(i));
AtomicFile af = new AtomicFile(file);
expectedFiles.add(af);
mDataBase.mHistoryFiles.addLast(af);
@@ -133,6 +134,7 @@
for (int i = 5; i >= 0; i--) {
File file = mock(File.class);
when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
+ when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i));
AtomicFile af = new AtomicFile(file);
mDataBase.mHistoryFiles.addLast(af);
}
@@ -158,6 +160,7 @@
for (long i = cal.getTimeInMillis(); i >= 5; i--) {
File file = mock(File.class);
when(file.getName()).thenReturn(i + ".bak");
+ when(file.getAbsolutePath()).thenReturn(i + ".bak");
AtomicFile af = new AtomicFile(file);
mDataBase.mHistoryFiles.addLast(af);
}
@@ -415,4 +418,36 @@
assertThat(mDataBase.mBuffer).isNotEqualTo(nh);
verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
}
+
+ @Test
+ public void testRemoveFilePathFromHistory_hasMatch() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ AtomicFile af = mock(AtomicFile.class);
+ when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+ mDataBase.mHistoryFiles.addLast(af);
+ }
+ // Baseline size of history files
+ assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+ // Remove only file number 3
+ String filePathToRemove = new File(mRootDir, "af3").getAbsolutePath();
+ mDataBase.removeFilePathFromHistory(filePathToRemove);
+ assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(4);
+ }
+
+ @Test
+ public void testRemoveFilePathFromHistory_noMatch() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ AtomicFile af = mock(AtomicFile.class);
+ when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
+ mDataBase.mHistoryFiles.addLast(af);
+ }
+ // Baseline size of history files
+ assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+
+ // Attempt to remove a filename that doesn't exist, expect nothing to break or change
+ String filePathToRemove = new File(mRootDir, "af.thisfileisfake").getAbsolutePath();
+ mDataBase.removeFilePathFromHistory(filePathToRemove);
+ assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 87efaa2..37d7198 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5057,7 +5057,8 @@
}
@Test
- public void testToastRateLimiterCanPreventShowCallForCustomToast() throws Exception {
+ public void testToastRateLimiterWontPreventShowCallForCustomToastWhenInForeground()
+ throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
@@ -5075,30 +5076,7 @@
INotificationManager nmService = (INotificationManager) mService.mService;
nmService.enqueueToast(testPackage, token, callback, 2000, 0);
- verify(callback, times(0)).show(any());
- }
-
- @Test
- public void testCustomToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
- final String testPackage = "testPackageName";
- assertEquals(0, mService.mToastQueue.size());
- mService.isSystemUid = false;
- setToastRateIsWithinQuota(false); // rate limit reached
- // Avoids rate limiting.
- setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
-
- // package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
- .thenReturn(false);
-
- setAppInForegroundForToasts(mUid, true);
-
- Binder token = new Binder();
- ITransientNotification callback = mock(ITransientNotification.class);
- INotificationManager nmService = (INotificationManager) mService.mService;
-
- nmService.enqueueToast(testPackage, token, callback, 2000, 0);
- verify(callback).show(any());
+ verify(callback, times(1)).show(any());
}
@Test
@@ -5206,12 +5184,14 @@
}
@Test
- public void testToastRateLimiterCanPreventShowCallForTextToast() throws Exception {
+ public void testToastRateLimiterCanPreventShowCallForTextToast_whenInBackground()
+ throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+ setAppInForegroundForToasts(mUid, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
@@ -5226,12 +5206,35 @@
}
@Test
+ public void testToastRateLimiterWontPreventShowCallForTextToast_whenInForeground()
+ throws Exception {
+ final String testPackage = "testPackageName";
+ assertEquals(0, mService.mToastQueue.size());
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(false); // rate limit reached
+ setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
+ setAppInForegroundForToasts(mUid, true);
+
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ .thenReturn(false);
+
+ Binder token = new Binder();
+ INotificationManager nmService = (INotificationManager) mService.mService;
+
+ nmService.enqueueTextToast(testPackage, token, "Text", 2000, 0, null);
+ verify(mStatusBar, times(1))
+ .showToast(anyInt(), any(), any(), any(), any(), anyInt(), any());
+ }
+
+ @Test
public void testTextToastRateLimiterAllowsLimitAvoidanceWithPermission() throws Exception {
final String testPackage = "testPackageName";
assertEquals(0, mService.mToastQueue.size());
mService.isSystemUid = false;
setToastRateIsWithinQuota(false); // rate limit reached
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, true);
+ setAppInForegroundForToasts(mUid, false);
// package is not suspended
when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7c2cfab..ee1d393 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1515,6 +1515,129 @@
assertEquals(primarySplitBounds, letterboxedBounds);
}
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_left() {
+ // Display configured as (2800, 1400).
+ assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxHorizontalPositionMultiplier */ 0.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
+ // After the display is resized to (700, 1400).
+ /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_center() {
+ // Display configured as (2800, 1400).
+ assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxHorizontalPositionMultiplier */ 0.5f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ // After the display is resized to (700, 1400).
+ /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() {
+ // Display configured as (2800, 1400).
+
+ // Below 0.0.
+ assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxHorizontalPositionMultiplier */ -1.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ // After the display is resized to (700, 1400).
+ /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+
+ // Above 1.0
+ assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxHorizontalPositionMultiplier */ 2.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
+ // After the display is resized to (700, 1400).
+ /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_right() {
+ // Display configured as (2800, 1400).
+ assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxHorizontalPositionMultiplier */ 1.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
+ // After the display is resized to (700, 1400).
+ /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
+ }
+
+ private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
+ float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox,
+ Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
+ // Set up a display in landscape and ignoring orientation request.
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.setLetterboxHorizontalPositionMultiplier(
+ letterboxHorizontalPositionMultiplier);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ assertTrue(mActivity.inSizeCompatMode());
+ // Activity is in size compat mode but not scaled.
+ assertEquals(sizeCompatUnscaled, mActivity.getBounds());
+
+ // Force activity to scaled down for size compat mode.
+ resizeDisplay(mTask.mDisplayContent, 700, 1400);
+
+ assertTrue(mActivity.inSizeCompatMode());
+ assertScaled();
+ assertEquals(sizeCompatScaled, mActivity.getBounds());
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
+ // When activity width equals parent width, multiplier shouldn't have any effect.
+ assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxHorizontalPositionMultiplier */ 0.0f);
+ assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxHorizontalPositionMultiplier */ 0.5f);
+ assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxHorizontalPositionMultiplier */ 1.0f);
+ }
+
+ private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ float letterboxHorizontalPositionMultiplier) {
+ // Set up a display in landscape and ignoring orientation request.
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.setLetterboxHorizontalPositionMultiplier(
+ letterboxHorizontalPositionMultiplier);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertFitted();
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ assertTrue(mActivity.inSizeCompatMode());
+ // Activity is in size compat mode but not scaled.
+ assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+ }
+
private static WindowState addWindowToActivity(ActivityRecord activity) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index dd0c9e6..d9aa871 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -23,6 +23,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -50,7 +51,13 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.view.IWindowSessionCallback;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.View;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -250,4 +257,31 @@
eq(clientToken), eq(windowToken), anyInt(), eq(TYPE_INPUT_METHOD),
eq(windowToken.mOptions));
}
+
+ @Test
+ public void testAddWindowWithSubWindowTypeByWindowContext() {
+ spyOn(mWm.mWindowContextListenerController);
+
+ final WindowToken windowToken = createTestWindowToken(TYPE_INPUT_METHOD, mDefaultDisplay);
+ final Session session = new Session(mWm, new IWindowSessionCallback.Stub() {
+ @Override
+ public void onAnimatorScaleChanged(float v) throws RemoteException {}
+ });
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ TYPE_APPLICATION_ATTACHED_DIALOG);
+ params.token = windowToken.token;
+ final IBinder windowContextToken = new Binder();
+ params.setWindowContextToken(windowContextToken);
+ doReturn(true).when(mWm.mWindowContextListenerController)
+ .hasListener(eq(windowContextToken));
+ doReturn(TYPE_INPUT_METHOD).when(mWm.mWindowContextListenerController)
+ .getWindowType(eq(windowContextToken));
+
+ mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
+ UserHandle.USER_SYSTEM, new InsetsState(), null, new InsetsState(),
+ new InsetsSourceControl[0]);
+
+ verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
+ any(), anyInt(), anyInt(), any());
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5bafbbd..ae12062 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -101,6 +101,7 @@
import com.android.internal.policy.AttributeCache;
import com.android.internal.util.ArrayUtils;
+import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.Description;
@@ -208,10 +209,25 @@
// {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set
// on some device form factors.
mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(0);
+ // Ensure letterbox position multiplier is not overridden on any device target.
+ // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
+ // may be set on some device form factors.
+ mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier(0.5f);
checkDeviceSpecificOverridesNotApplied();
}
+ @After
+ public void tearDown() throws Exception {
+ // Revert back to device overrides.
+ mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(
+ mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio));
+ mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier(
+ mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier));
+ }
+
/**
* Check that device-specific overrides are not applied. Only need to check once during entire
* test run for each case: global overrides, default display, and test display.
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 2e692e6..7f24c36 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -2139,6 +2139,12 @@
Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+ "forceRestart=" + forceRestart);
}
+ if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
+ if ((functions & UsbManager.FUNCTION_NCM) != 0) {
+ Slog.e(TAG, "Could not set unsupported function for the GadgetHal");
+ return;
+ }
+ }
if (mCurrentFunctions != functions
|| !mCurrentFunctionsApplied
|| forceRestart) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index bcfb302..6f701f7 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -434,7 +434,8 @@
}
try {
AudioRecord audioRecord = new AudioRecord(
- new AudioAttributes.Builder().setHotwordModeEnabled(true).build(),
+ new AudioAttributes.Builder()
+ .setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD).build(),
audioFormat,
getBufferSizeInBytes(
audioFormat.getSampleRate(),
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index f6d18fc..96e715e 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -637,18 +637,18 @@
this.band = band;
this.downlinkLowKhz = downlinkLowKhz;
this.downlinkOffset = downlinkOffset;
+ this.downlinkRange = downlinkRange;
this.uplinkLowKhz = uplinkLowKhz;
this.uplinkOffset = uplinkOffset;
- this.downlinkRange = downlinkRange;
this.uplinkRange = uplinkRange;
}
int band;
int downlinkLowKhz;
int downlinkOffset;
+ int downlinkRange;
int uplinkLowKhz;
int uplinkOffset;
- int downlinkRange;
int uplinkRange;
}
diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index f29f3bd..6b82045 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -598,7 +598,8 @@
: earfcnFrequency.downlinkOffset;
break;
} else {
- Log.e(TAG, "Band and the range of EARFCN are not consistent.");
+ Rlog.w(TAG,"Band and the range of EARFCN are not consistent: band = " + band
+ + " ,earfcn = " + earfcn + " ,isUplink = " + isUplink);
return INVALID_FREQUENCY;
}
}
@@ -617,7 +618,7 @@
}
private static boolean isInEarfcnRange(int earfcn, EutranBandArfcnFrequency earfcnFrequency,
- boolean isUplink) {
+ boolean isUplink) {
if (isUplink) {
return earfcn >= earfcnFrequency.uplinkOffset && earfcn <= earfcnFrequency.uplinkRange;
} else {
@@ -640,7 +641,8 @@
: uarfcnFrequency.downlinkOffset;
break;
} else {
- Log.e(TAG, "Band and the range of UARFCN are not consistent.");
+ Rlog.w(TAG,"Band and the range of UARFCN are not consistent: band = " + band
+ + " ,uarfcn = " + uarfcn + " ,isUplink = " + isUplink);
return INVALID_FREQUENCY;
}
}
@@ -716,7 +718,8 @@
arfcnOffset);
break;
} else {
- Log.e(TAG, "Band and the range of ARFCN are not consistent.");
+ Rlog.w(TAG,"Band and the range of ARFCN are not consistent: band = " + band
+ + " ,arfcn = " + arfcn + " ,isUplink = " + isUplink);
return INVALID_FREQUENCY;
}
}
@@ -733,7 +736,7 @@
* Downlink actual frequency(kHz) = Uplink actual frequency + 10
*/
private static int convertArfcnToFrequency(int arfcn, int uplinkFrequencyFirstKhz,
- int arfcnOffset) {
+ int arfcnOffset) {
return uplinkFrequencyFirstKhz + 200 * (arfcn - arfcnOffset);
}
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index c8ed82c..0539897 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -1055,6 +1055,20 @@
*/
public static final int HANDOVER_FAILED = 0x10006;
+ /**
+ * Enterprise setup failure: duplicate CID in DataCallResponse.
+ *
+ * @hide
+ */
+ public static final int DUPLICATE_CID = 0x10007;
+
+ /**
+ * Enterprise setup failure: no default data connection set up yet.
+ *
+ * @hide
+ */
+ public static final int NO_DEFAULT_DATA = 0x10008;
+
private static final Map<Integer, String> sFailCauseMap;
static {
sFailCauseMap = new HashMap<>();
@@ -1481,6 +1495,9 @@
sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER,
"UNACCEPTABLE_NETWORK_PARAMETER");
sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION");
+ sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED");
+ sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID");
+ sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA");
}
private DataFailCause() {
@@ -1580,6 +1597,7 @@
add(RADIO_NOT_AVAILABLE);
add(UNACCEPTABLE_NETWORK_PARAMETER);
add(SIGNAL_LOST);
+ add(DUPLICATE_CID);
}
};
}
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index ec85995..2d230a7 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -23,6 +23,7 @@
import android.app.blob.LeaseInfo;
import android.content.Context;
import android.content.res.Resources;
+import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.util.Log;
@@ -56,6 +57,16 @@
}
}
+ public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input)
+ throws IOException {
+ try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
+ try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
+ session.openWrite(0, -1))) {
+ FileUtils.copy(in, out);
+ }
+ }
+ }
+
public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input,
long lengthBytes) throws IOException {
try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index c92d40c..db91eb2 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -16,12 +16,14 @@
package com.android.server.wm.flicker.close
+import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.dsl.FlickerBuilder
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -44,6 +46,30 @@
}
}
+ @FlakyTest(bugId = 185401242)
+ @Test
+ override fun launcherLayerReplacesApp() {
+ super.launcherLayerReplacesApp()
+ }
+
+ @FlakyTest(bugId = 185401242)
+ @Test
+ override fun launcherReplacesAppWindowAsTopWindow() {
+ super.launcherReplacesAppWindowAsTopWindow()
+ }
+
+ @FlakyTest(bugId = 185401242)
+ @Test
+ override fun launcherWindowBecomesVisible() {
+ super.launcherWindowBecomesVisible()
+ }
+
+ @FlakyTest(bugId = 185401242)
+ @Test
+ override fun noUncoveredRegions() {
+ super.noUncoveredRegions()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
index 0bae8f6..3bd19ea 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeAutoOpenWindowToAppTest.kt
@@ -51,6 +51,7 @@
@RunWith(Parameterized::class)
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 185400889)
class CloseImeAutoOpenWindowToAppTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.config.startRotation)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index 1c97be3..ad7ee30 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -98,6 +98,12 @@
super.focusChanges()
}
+ @FlakyTest(bugId = 185400889)
+ @Test
+ override fun noUncoveredRegions() {
+ super.noUncoveredRegions()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index 742a2d1..a353c59 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -16,7 +16,6 @@
package com.android.server.wm.flicker.rotation
-import android.platform.test.annotations.Presubmit
import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -69,13 +68,13 @@
super.statusBarLayerIsAlwaysVisible()
}
- @FlakyTest
+ @FlakyTest(bugId = 185400889)
@Test
override fun noUncoveredRegions() {
super.noUncoveredRegions()
}
- @Presubmit
+ @FlakyTest(bugId = 185400889)
@Test
fun appLayerAlwaysVisible() {
testSpec.assertLayers {
@@ -83,7 +82,7 @@
}
}
- @Presubmit
+ @FlakyTest(bugId = 185400889)
@Test
fun appLayerRotates() {
testSpec.assertLayers {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 0bb0337..642b19e 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -527,6 +527,33 @@
}
@Test
+ public void testExpireSession_Phase1_Install() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+ }
+
+ @Test
+ public void testExpireSession_Phase2_VerifyInstall() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ assertThat(rollback).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+ assertThat(rollback.isStaged()).isTrue();
+ }
+
+ @Test
+ public void testExpireSession_Phase3_VerifyRollback() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ RollbackInfo rollback = getUniqueRollbackInfoForPackage(
+ rm.getAvailableRollbacks(), TestApp.A);
+ assertThat(rollback).isNotNull();
+ }
+
+ @Test
public void hasMainlineModule() throws Exception {
String pkgName = getModuleMetadataPackageName();
boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 304567a3..1aa5c24 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -40,7 +40,9 @@
import org.junit.runner.RunWith;
import java.io.File;
+import java.time.Instant;
import java.util.Collections;
+import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -582,6 +584,27 @@
runPhase("testWatchdogMonitorsAcrossReboots_Phase3_VerifyRollback");
}
+ /**
+ * Tests an available rollback shouldn't be deleted when its session expires.
+ */
+ @Test
+ public void testExpireSession() throws Exception {
+ runPhase("testExpireSession_Phase1_Install");
+ getDevice().reboot();
+ runPhase("testExpireSession_Phase2_VerifyInstall");
+
+ // Advance system clock by 7 days to expire the staged session
+ Instant t1 = Instant.ofEpochMilli(getDevice().getDeviceDate());
+ Instant t2 = t1.plusMillis(TimeUnit.DAYS.toMillis(7));
+ runAsRoot(() -> getDevice().setDate(Date.from(t2)));
+
+ // Somehow we need to wait for a while before reboot. Otherwise the change to the
+ // system clock will be reset after reboot.
+ Thread.sleep(3000);
+ getDevice().reboot();
+ runPhase("testExpireSession_Phase3_VerifyRollback");
+ }
+
private void pushTestApex() throws Exception {
CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
diff --git a/tests/UpdatableSystemFontTest/AndroidTest.xml b/tests/UpdatableSystemFontTest/AndroidTest.xml
index d573e93..4f11669 100644
--- a/tests/UpdatableSystemFontTest/AndroidTest.xml
+++ b/tests/UpdatableSystemFontTest/AndroidTest.xml
@@ -19,6 +19,11 @@
<!-- This test requires root to side load fs-verity cert. -->
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="EmojiRenderingTestApp.apk" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="UpdatableSystemFontTestCert.der->/data/local/tmp/UpdatableSystemFontTestCert.der" />
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
new file mode 100644
index 0000000..ed34fa9
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/Android.bp
@@ -0,0 +1,32 @@
+// 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 {
+ // 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_test_helper_app {
+ name: "EmojiRenderingTestApp",
+ manifest: "AndroidManifest.xml",
+ srcs: ["src/**/*.java"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/AndroidManifest.xml b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5d8f5fc
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/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.emojirenderingtestapp">
+ <application>
+ <activity android:name=".EmojiRenderingTestActivity"/>
+ </application>
+</manifest>
diff --git a/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java
new file mode 100644
index 0000000..947e9c2
--- /dev/null
+++ b/tests/UpdatableSystemFontTest/EmojiRenderingTestApp/src/com/android/emojirenderingtestapp/EmojiRenderingTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.emojirenderingtestapp;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/** Test app to render an emoji. */
+public class EmojiRenderingTestActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout container = new LinearLayout(this);
+ container.setOrientation(LinearLayout.VERTICAL);
+ TextView textView = new TextView(this);
+ textView.setText("\uD83E\uDD72"); // 🥲
+ container.addView(textView, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ setContentView(container);
+ }
+}
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index e684556..032da3f 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -36,7 +36,6 @@
import org.junit.runner.RunWith;
import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -47,6 +46,9 @@
@RunWith(DeviceJUnit4ClassRunner.class)
public class UpdatableSystemFontTest extends BaseHostJUnit4Test {
+ private static final String SYSTEM_FONTS_DIR = "/system/fonts/";
+ private static final String DATA_FONTS_DIR = "/data/fonts/files/";
+
private static final String CERT_PATH = "/data/local/tmp/UpdatableSystemFontTestCert.der";
private static final Pattern PATTERN_FONT = Pattern.compile("path = ([^, \n]*)");
@@ -72,6 +74,14 @@
private static final String TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG =
"/data/local/tmp/UpdatableSystemFontTestNotoColorEmojiVPlus2.ttf.fsv_sig";
+ private static final String EMOJI_RENDERING_TEST_APP_ID = "com.android.emojirenderingtestapp";
+ private static final String EMOJI_RENDERING_TEST_ACTIVITY =
+ EMOJI_RENDERING_TEST_APP_ID + "/.EmojiRenderingTestActivity";
+
+ private interface ThrowingSupplier<T> {
+ T get() throws Exception;
+ }
+
@Rule
public final AddFsVerityCertRule mAddFsverityCertRule =
new AddFsVerityCertRule(this, CERT_PATH);
@@ -91,7 +101,10 @@
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
- assertThat(fontPath).startsWith("/data/fonts/files/");
+ assertThat(fontPath).startsWith(DATA_FONTS_DIR);
+ // The updated font should be readable and unmodifiable.
+ expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
+ expectRemoteCommandToFail("echo -n '' >> " + fontPath);
}
@Test
@@ -102,8 +115,12 @@
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
String fontPath2 = getFontPath(NOTO_COLOR_EMOJI_TTF);
- assertThat(fontPath2).startsWith("/data/fonts/files/");
+ assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
assertThat(fontPath2).isNotEqualTo(fontPath);
+ // The new file should be readable.
+ expectRemoteCommandToSucceed("cat " + fontPath2 + " > /dev/null");
+ // The old file should be still readable.
+ expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
}
@Test
@@ -119,25 +136,14 @@
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath3 = getFontPath(NOTO_COLOR_EMOJI_TTF);
- assertThat(fontPath).startsWith("/data/fonts/files/");
+ assertThat(fontPath).startsWith(DATA_FONTS_DIR);
assertThat(fontPath2).isNotEqualTo(fontPath);
- assertThat(fontPath2).startsWith("/data/fonts/files/");
- assertThat(fontPath3).startsWith("/data/fonts/files/");
+ assertThat(fontPath2).startsWith(DATA_FONTS_DIR);
+ assertThat(fontPath3).startsWith(DATA_FONTS_DIR);
assertThat(fontPath3).isNotEqualTo(fontPath);
}
@Test
- public void updatedFont_dataFileIsImmutableAndReadable() throws Exception {
- expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
- TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
- String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
- assertThat(fontPath).startsWith("/data");
-
- expectRemoteCommandToFail("echo -n '' >> " + fontPath);
- expectRemoteCommandToSucceed("cat " + fontPath + " > /dev/null");
- }
-
- @Test
public void updateFont_invalidCert() throws Exception {
expectRemoteCommandToFail(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS2_TTF_FSV_SIG));
@@ -158,11 +164,37 @@
}
@Test
+ public void launchApp() throws Exception {
+ String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(fontPath).startsWith(SYSTEM_FONTS_DIR);
+ expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
+ expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+ waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
+ isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID));
+ }
+
+ @Test
+ public void launchApp_afterUpdateFont() throws Exception {
+ String originalFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(originalFontPath).startsWith(SYSTEM_FONTS_DIR);
+ expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
+ TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
+ String updatedFontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
+ assertThat(updatedFontPath).startsWith(DATA_FONTS_DIR);
+ expectRemoteCommandToSucceed("am force-stop " + EMOJI_RENDERING_TEST_APP_ID);
+ expectRemoteCommandToSucceed("am start-activity -n " + EMOJI_RENDERING_TEST_ACTIVITY);
+ // The original font should NOT be opened by the app.
+ waitUntil(TimeUnit.SECONDS.toMillis(5), () ->
+ isFileOpenedBy(updatedFontPath, EMOJI_RENDERING_TEST_APP_ID)
+ && !isFileOpenedBy(originalFontPath, EMOJI_RENDERING_TEST_APP_ID));
+ }
+
+ @Test
public void reboot() throws Exception {
expectRemoteCommandToSucceed(String.format("cmd font update %s %s",
TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF, TEST_NOTO_COLOR_EMOJI_VPLUS1_TTF_FSV_SIG));
String fontPath = getFontPath(NOTO_COLOR_EMOJI_TTF);
- assertThat(fontPath).startsWith("/data/fonts/files/");
+ assertThat(fontPath).startsWith(DATA_FONTS_DIR);
expectRemoteCommandToSucceed("stop");
expectRemoteCommandToSucceed("start");
@@ -210,16 +242,40 @@
});
}
- private void waitUntil(long timeoutMillis, Supplier<Boolean> func) {
+ private void waitUntil(long timeoutMillis, ThrowingSupplier<Boolean> func) {
long untilMillis = System.currentTimeMillis() + timeoutMillis;
do {
- if (func.get()) return;
try {
+ if (func.get()) return;
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError("Interrupted", e);
+ } catch (Exception e) {
+ throw new AssertionError("Unexpected exception", e);
}
} while (System.currentTimeMillis() < untilMillis);
throw new AssertionError("Timed out");
}
+
+ private boolean isFileOpenedBy(String path, String appId) throws DeviceNotAvailableException {
+ String pid = pidOf(appId);
+ if (pid.isEmpty()) {
+ return false;
+ }
+ CommandResult result = getDevice().executeShellV2Command(
+ String.format("lsof -t -p %s '%s'", pid, path));
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ return false;
+ }
+ // The file is open if the output of lsof is non-empty.
+ return !result.getStdout().trim().isEmpty();
+ }
+
+ private String pidOf(String appId) throws DeviceNotAvailableException {
+ CommandResult result = getDevice().executeShellV2Command("pidof " + appId);
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ return "";
+ }
+ return result.getStdout().trim();
+ }
}
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index 454d5b5..2b45b3d 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -44,6 +44,9 @@
setSubscriberId("MySubId")
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
+ if (isAtLeastS()) {
+ setBypassableVpn(true)
+ }
}.build()
if (isAtLeastS()) {
// From S, the config will have 12 items
@@ -66,6 +69,7 @@
if (isAtLeastS()) {
setNat64DetectionEnabled(false)
setProvisioningNotificationEnabled(false)
+ setBypassableVpn(true)
}
}.build()
@@ -78,6 +82,7 @@
if (isAtLeastS()) {
assertFalse(config.isNat64DetectionEnabled())
assertFalse(config.isProvisioningNotificationEnabled())
+ assertTrue(config.isBypassableVpn())
} else {
assertTrue(config.isNat64DetectionEnabled())
assertTrue(config.isProvisioningNotificationEnabled())
diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp
index 730ef3b..39c424e 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -62,6 +62,7 @@
// Utilities for testing framework code both in integration and unit tests.
java_library {
name: "frameworks-net-integration-testutils",
+ defaults: ["framework-connectivity-test-defaults"],
srcs: ["util/**/*.java", "util/**/*.kt"],
static_libs: [
"androidx.annotation_annotation",
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 6cbdd25..19f8843 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -384,7 +384,7 @@
eq(TRACK_DEFAULT.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
- manager.requestBackgroundNetwork(request, handler, callback);
+ manager.requestBackgroundNetwork(request, callback, handler);
verify(mService).requestNetwork(eq(Process.INVALID_UID), eq(request.networkCapabilities),
eq(BACKGROUND_REQUEST.ordinal()), any(), anyInt(), any(), eq(TYPE_NONE), anyInt(),
eq(testPkgName), eq(testAttributionTag));
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 47e4b5e..039ce2f 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4313,7 +4313,7 @@
final TestNetworkCallback cellBgCallback = new TestNetworkCallback();
mCm.requestBackgroundNetwork(new NetworkRequest.Builder()
.addTransportType(TRANSPORT_CELLULAR).build(),
- mCsHandlerThread.getThreadHandler(), cellBgCallback);
+ cellBgCallback, mCsHandlerThread.getThreadHandler());
// Make callbacks for monitoring.
final NetworkRequest request = new NetworkRequest.Builder().build();
@@ -9768,7 +9768,8 @@
return new NetworkAgentInfo(null, new Network(NET_ID), info, new LinkProperties(),
nc, new NetworkScore.Builder().setLegacyInt(0).build(),
mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0,
- INVALID_UID, mQosCallbackTracker, new ConnectivityService.Dependencies());
+ INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker,
+ new ConnectivityService.Dependencies());
}
@Test
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 116d755e..36e229d 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -71,6 +71,8 @@
static final int LOW_DAILY_LIMIT = 2;
static final int HIGH_DAILY_LIMIT = 1000;
+ private static final int TEST_LINGER_DELAY_MS = 400;
+
LingerMonitor mMonitor;
@Mock ConnectivityService mConnService;
@@ -366,7 +368,7 @@
NetworkAgentInfo nai = new NetworkAgentInfo(null, new Network(netId), info,
new LinkProperties(), caps, new NetworkScore.Builder().setLegacyInt(50).build(),
mCtx, null, new NetworkAgentConfig.Builder().build(), mConnService, mNetd,
- mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(),
+ mDnsResolver, NetworkProvider.ID_NONE, Binder.getCallingUid(), TEST_LINGER_DELAY_MS,
mQosCallbackTracker, new ConnectivityService.Dependencies());
nai.everValidated = true;
return nai;
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
index b592000..0c7363e 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -153,21 +153,19 @@
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getWifiRequest(expectedSubIds)),
- any(),
- any(NetworkBringupCallback.class));
+ any(NetworkBringupCallback.class),
+ any());
for (final int subId : expectedSubIds) {
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getCellRequestForSubId(subId)),
- any(),
- any(NetworkBringupCallback.class));
+ any(NetworkBringupCallback.class), any());
}
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getRouteSelectionRequest(expectedSubIds)),
- any(),
- any(RouteSelectionCallback.class));
+ any(RouteSelectionCallback.class), any());
}
@Test
@@ -267,8 +265,8 @@
verify(mConnectivityManager)
.requestBackgroundNetwork(
eq(getRouteSelectionRequest(INITIAL_SUB_IDS)),
- any(),
- mRouteSelectionCallbackCaptor.capture());
+ mRouteSelectionCallbackCaptor.capture(),
+ any());
RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue();
cb.onAvailable(mNetwork);
diff --git a/tools/hiddenapi/checksorted_sha.sh b/tools/hiddenapi/checksorted_sha.sh
index 451fed6..72fb867 100755
--- a/tools/hiddenapi/checksorted_sha.sh
+++ b/tools/hiddenapi/checksorted_sha.sh
@@ -1,10 +1,10 @@
#!/bin/bash
set -e
LOCAL_DIR="$( dirname ${BASH_SOURCE} )"
-git show --name-only --pretty=format: $1 | grep "boot/hiddenapi/hiddenapi-.*txt" | while read file; do
+git show --name-only --pretty=format: $1 | grep "hiddenapi/hiddenapi-.*txt" | while read file; do
diff <(git show $1:$file) <(git show $1:$file | $LOCAL_DIR/sort_api.sh ) || {
echo -e "\e[1m\e[31m$file $1 is not sorted or contains duplicates. To sort it correctly:\e[0m"
- echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $2/frameworks/base/$file\e[0m"
+ echo -e "\e[33m${LOCAL_DIR}/sort_api.sh $PWD/$file\e[0m"
exit 1
}
done
diff --git a/tools/hiddenapi/exclude.sh b/tools/hiddenapi/exclude.sh
index 822aba4..8b18f9b 100755
--- a/tools/hiddenapi/exclude.sh
+++ b/tools/hiddenapi/exclude.sh
@@ -10,7 +10,6 @@
android.system \
android.test \
com.android.bouncycastle \
- com.android.i18n.phonenumbers \
com.android.okhttp \
com.sun \
dalvik \