diff options
317 files changed, 7392 insertions, 3124 deletions
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java index b66837d1f679..b06e21516cac 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 @@ import java.util.Objects; * @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 @@ public final class AppSearchResult<ValueType> implements Parcelable { @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 @@ public final class AppSearchResult<ValueType> implements Parcelable { } 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 ac91bdb2dce7..c85c4c3a8a7f 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 @@ package android.app.appsearch; 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 @@ public final class AppSearchSession implements Closeable { 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 @@ public final class AppSearchSession implements Closeable { request.getProjectionsInternal(), mUserId, new IAppSearchBatchResultCallback.Stub() { + @Override public void onResult(AppSearchBatchResult result) { executor.execute(() -> { AppSearchBatchResult.Builder<String, GenericDocument> @@ -359,8 +362,9 @@ public final class AppSearchSession implements Closeable { }); } - 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 @@ public final class AppSearchSession implements Closeable { 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 @@ public final class AppSearchSession implements Closeable { } }); } + + /** + * 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 49049b641839..28f8a7a78f4a 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 @@ public interface BatchResultCallback<KeyType, ValueType> { 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 b1bbd18b98e2..64b331ea374c 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 27729a5ad058..299c9957974e 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 a8048dc5a4c4..2368bdb7466d 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 @@ public final class AppSearchSchema { /** @hide */ public AppSearchSchema(@NonNull Bundle bundle) { - Preconditions.checkNotNull(bundle); + Objects.requireNonNull(bundle); mBundle = bundle; } @@ -125,7 +125,7 @@ public final class AppSearchSchema { /** Creates a new {@link AppSearchSchema.Builder}. */ public Builder(@NonNull String schemaType) { - Preconditions.checkNotNull(schemaType); + Objects.requireNonNull(schemaType); mSchemaType = schemaType; } @@ -133,7 +133,7 @@ public final class AppSearchSchema { @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 @@ public final class AppSearchSchema { @Nullable private Integer mHashCode; PropertyConfig(@NonNull Bundle bundle) { - mBundle = Preconditions.checkNotNull(bundle); + mBundle = Objects.requireNonNull(bundle); } @Override @@ -712,7 +712,7 @@ public final class AppSearchSchema { /** 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 @@ public final class AppSearchSchema { @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 8c9d950abe25..e3b3a859981d 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.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import java.util.Set; /** @@ -101,11 +102,11 @@ public class GenericDocument { * @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 @@ public class GenericDocument { */ @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 @@ public class GenericDocument { */ @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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { * 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 @@ public class GenericDocument { */ @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 @@ public class GenericDocument { */ @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 @@ public class GenericDocument { */ @Nullable public String[] getPropertyStringArray(@NonNull String key) { - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return getAndCastPropertyArray(key, String[].class); } @@ -355,7 +356,7 @@ public class GenericDocument { */ @Nullable public long[] getPropertyLongArray(@NonNull String key) { - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return getAndCastPropertyArray(key, long[].class); } @@ -368,7 +369,7 @@ public class GenericDocument { */ @Nullable public double[] getPropertyDoubleArray(@NonNull String key) { - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return getAndCastPropertyArray(key, double[].class); } @@ -381,7 +382,7 @@ public class GenericDocument { */ @Nullable public boolean[] getPropertyBooleanArray(@NonNull String key) { - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return getAndCastPropertyArray(key, boolean[].class); } @@ -396,7 +397,7 @@ public class GenericDocument { @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 @@ public class GenericDocument { @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 @@ public class GenericDocument { */ @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 @@ public class GenericDocument { @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 @@ public class GenericDocument { * * @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 @@ public class GenericDocument { * * @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 @@ public class GenericDocument { * * @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 @@ public class GenericDocument { @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 class GenericDocument { 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 @@ public class GenericDocument { } 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 1719e14b01e3..4dc3225bd179 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.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -52,9 +53,9 @@ public final class GetByUriRequest { @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 @@ public final class GetByUriRequest { /** Creates a {@link GetByUriRequest.Builder} instance. */ public Builder(@NonNull String namespace) { - mNamespace = Preconditions.checkNotNull(namespace); + mNamespace = Objects.requireNonNull(namespace); } /** @@ -124,7 +125,7 @@ public final class GetByUriRequest { */ @NonNull public Builder addUris(@NonNull String... uris) { - Preconditions.checkNotNull(uris); + Objects.requireNonNull(uris); return addUris(Arrays.asList(uris)); } @@ -136,7 +137,7 @@ public final class GetByUriRequest { @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 final class GetByUriRequest { 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 1f56ef3588b1..691ef4ff939f 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 android.util.ArraySet; 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 @@ public class GetSchemaResponse { 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 bfb9323047c8..4f63baeeb41c 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.annotation.NonNull; 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 @@ public class PackageIdentifier { /** @hide */ public PackageIdentifier(@NonNull Bundle bundle) { - mBundle = Preconditions.checkNotNull(bundle); + mBundle = Objects.requireNonNull(bundle); } /** @hide */ @@ -54,12 +54,12 @@ public class PackageIdentifier { @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 01473be062bc..b49e0e8ca2cf 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.Arrays; 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 @@ public final class PutDocumentsRequest { */ @NonNull public Builder addGenericDocuments(@NonNull GenericDocument... documents) { - Preconditions.checkNotNull(documents); + Objects.requireNonNull(documents); return addGenericDocuments(Arrays.asList(documents)); } @@ -74,7 +75,7 @@ public final class PutDocumentsRequest { 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 8da68c0b4898..4dcad68d49be 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 com.android.internal.util.Preconditions; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Objects; import java.util.Set; /** @@ -65,7 +66,7 @@ public final class RemoveByUriRequest { /** Creates a {@link RemoveByUriRequest.Builder} instance. */ public Builder(@NonNull String namespace) { - mNamespace = Preconditions.checkNotNull(namespace); + mNamespace = Objects.requireNonNull(namespace); } /** @@ -75,7 +76,7 @@ public final class RemoveByUriRequest { */ @NonNull public Builder addUris(@NonNull String... uris) { - Preconditions.checkNotNull(uris); + Objects.requireNonNull(uris); return addUris(Arrays.asList(uris)); } @@ -87,7 +88,7 @@ public final class RemoveByUriRequest { @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 2e152f89465f..8aff3b41cbc7 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 android.annotation.NonNull; 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 @@ public final class ReportSystemUsageRequest { @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 @@ public final class ReportSystemUsageRequest { /** 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 @@ public final class ReportSystemUsageRequest { @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 @@ public final class ReportSystemUsageRequest { @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 646e73c24bd9..925bde92d69d 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 android.annotation.NonNull; import com.android.internal.util.Preconditions; +import java.util.Objects; + /** * A request to report usage of a document. * @@ -33,8 +35,8 @@ public final class ReportUsageRequest { 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 @@ public final class ReportUsageRequest { /** Creates a {@link ReportUsageRequest.Builder} instance. */ public Builder(@NonNull String namespace) { - mNamespace = Preconditions.checkNotNull(namespace); + mNamespace = Objects.requireNonNull(namespace); } /** @@ -82,7 +84,7 @@ public final class ReportUsageRequest { @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 @@ public final class ReportUsageRequest { @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 55a228d94c10..432f838bc78c 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 @@ public final class SearchResult { /** @hide */ public SearchResult(@NonNull Bundle bundle) { - mBundle = Preconditions.checkNotNull(bundle); + mBundle = Objects.requireNonNull(bundle); } /** @hide */ @@ -77,8 +77,7 @@ public final class SearchResult { 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 final class SearchResult { 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 @@ public final class SearchResult { */ @NonNull public String getPackageName() { - return Preconditions.checkNotNull(mBundle.getString(PACKAGE_NAME_FIELD)); + return Objects.requireNonNull(mBundle.getString(PACKAGE_NAME_FIELD)); } /** @@ -122,7 +121,7 @@ public final class SearchResult { */ @NonNull public String getDatabaseName() { - return Preconditions.checkNotNull(mBundle.getString(DATABASE_NAME_FIELD)); + return Objects.requireNonNull(mBundle.getString(DATABASE_NAME_FIELD)); } /** @@ -169,8 +168,8 @@ public final class SearchResult { * @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 @@ public final class SearchResult { @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 @@ public final class SearchResult { 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 @@ public final class SearchResult { @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 @@ public final class SearchResult { @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 dbd09d6bb91b..4853b5b03229 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.NonNull; 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 @@ public class SearchResultPage { @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 19d94305b3da..d466bf1359d0 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.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -176,7 +177,7 @@ public final class SearchSpec { /** @hide */ public SearchSpec(@NonNull Bundle bundle) { - Preconditions.checkNotNull(bundle); + Objects.requireNonNull(bundle); mBundle = bundle; } @@ -342,7 +343,7 @@ public final class SearchSpec { */ @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 @@ public final class SearchSpec { */ @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 @@ public final class SearchSpec { */ @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 @@ public final class SearchSpec { */ @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 @@ public final class SearchSpec { */ @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 @@ public final class SearchSpec { */ @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 final class SearchSpec { 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 5672bc7fdc13..8f7a0bf61f3e 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.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -94,10 +95,10 @@ public final class SetSchemaRequest { @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 @@ public final class SetSchemaRequest { */ @NonNull public Builder addSchemas(@NonNull AppSearchSchema... schemas) { - Preconditions.checkNotNull(schemas); + Objects.requireNonNull(schemas); return addSchemas(Arrays.asList(schemas)); } @@ -206,7 +207,7 @@ public final class SetSchemaRequest { @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 @@ public final class SetSchemaRequest { @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 @@ public final class SetSchemaRequest { @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 @@ public final class SetSchemaRequest { @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 @@ public final class SetSchemaRequest { */ @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 d63e4372f61f..7be589f727ce 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.ArrayList; 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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { if (mDeletedTypes == null) { mDeletedTypes = new ArraySet<>( - Preconditions.checkNotNull( + Objects.requireNonNull( mBundle.getStringArrayList(DELETED_TYPES_FIELD))); } return Collections.unmodifiableSet(mDeletedTypes); @@ -118,7 +119,7 @@ public class SetSchemaResponse { if (mMigratedTypes == null) { mMigratedTypes = new ArraySet<>( - Preconditions.checkNotNull( + Objects.requireNonNull( mBundle.getStringArrayList(MIGRATED_TYPES_FIELD))); } return Collections.unmodifiableSet(mMigratedTypes); @@ -139,7 +140,7 @@ public class SetSchemaResponse { if (mIncompatibleTypes == null) { mIncompatibleTypes = new ArraySet<>( - Preconditions.checkNotNull( + Objects.requireNonNull( mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD))); } return Collections.unmodifiableSet(mIncompatibleTypes); @@ -173,7 +174,7 @@ public class SetSchemaResponse { 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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 @@ public class SetSchemaResponse { @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 class SetSchemaResponse { 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 dc04cf3068ce..502b9391893f 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 android.os.Bundle; import com.android.internal.util.Preconditions; +import java.util.Objects; + /** The response class of {@code AppSearchSession#getStorageInfo}. */ public class StorageInfo { @@ -31,7 +33,7 @@ public class StorageInfo { 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 32d7e043e954..10e014bf9c9a 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 @@ import java.util.Set; 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 509877e21862..4ae16aa18305 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.SearchResultPage; import android.app.appsearch.SearchSpec; import android.app.appsearch.SetSchemaResponse; import android.app.appsearch.StorageInfo; -import android.app.appsearch.exceptions.AppSearchException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -46,7 +44,6 @@ import android.content.pm.PackageManagerInternal; 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,6 +51,7 @@ import android.os.UserManager; 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; @@ -64,6 +62,8 @@ import com.android.server.appsearch.external.localstorage.stats.CallStats; 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; @@ -121,14 +121,6 @@ public class AppSearchManagerService extends SystemService { 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 +128,15 @@ public class AppSearchManagerService extends SystemService { 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 +156,9 @@ public class AppSearchManagerService extends SystemService { 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); } } @@ -364,6 +321,8 @@ public class AppSearchManagerService extends SystemService { ++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); @@ -631,7 +590,7 @@ public class AppSearchManagerService extends SystemService { } } } - impl.persistToDisk(); + impl.persistToDisk(PersistType.Code.FULL); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(migrationFailureBundles)); } catch (Throwable t) { @@ -708,6 +667,8 @@ public class AppSearchManagerService extends SystemService { 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); @@ -741,6 +702,8 @@ public class AppSearchManagerService extends SystemService { 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); @@ -785,7 +748,7 @@ public class AppSearchManagerService extends SystemService { 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); } @@ -873,13 +836,12 @@ public class AppSearchManagerService extends SystemService { /** * 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 1ed26d670f36..c1b829428467 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -36,6 +36,7 @@ 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; @@ -153,7 +154,7 @@ public class VisibilityStore { * 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 = ""; 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 5f8cbee3dee3..50ac054a05fc 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 android.util.Log; 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.OptimizeResultProto; import com.google.android.icing.proto.PersistToDiskResultProto; import com.google.android.icing.proto.PersistType; import com.google.android.icing.proto.PropertyConfigProto; -import com.google.android.icing.proto.PropertyProto; import com.google.android.icing.proto.PutResultProto; import com.google.android.icing.proto.ReportUsageResultProto; import com.google.android.icing.proto.ResetResultProto; @@ -89,6 +95,7 @@ import java.util.Collections; 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 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; 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 @@ public final class AppSearchImpl implements Closeable { @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 @@ public final class AppSearchImpl implements Closeable { 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 @@ public final class AppSearchImpl implements Closeable { // 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 @@ public final class AppSearchImpl implements Closeable { return; } - persistToDisk(); + persistToDisk(PersistType.Code.FULL); mIcingSearchEngineLocked.close(); mClosedLocked = true; } catch (AppSearchException e) { @@ -364,7 +368,14 @@ public final class AppSearchImpl implements Closeable { } // 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 @@ public final class AppSearchImpl implements Closeable { mReadWriteLock.readLock().lock(); try { throwIfClosedLocked(); - + String prefix = createPrefix(packageName, databaseName); List<TypePropertyMask> nonPrefixedPropertyMasks = TypePropertyPathToProtoConverter.toTypePropertyMaskList(typePropertyPaths); List<TypePropertyMask> prefixedPropertyMasks = @@ -597,7 +608,7 @@ public final class AppSearchImpl implements Closeable { 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 @@ public final class AppSearchImpl implements Closeable { .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 @@ public final class AppSearchImpl implements Closeable { } } 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 @@ public final class AppSearchImpl implements Closeable { searchSpecBuilder.build(), scoringSpec, resultSpecBuilder.build()); checkSuccess(searchResultProto.getStatus()); - return rewriteSearchResultProto(searchResultProto); + return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked); } /** @@ -838,7 +851,7 @@ public final class AppSearchImpl implements Closeable { SearchResultProto searchResultProto = mIcingSearchEngineLocked.getNextPage(nextPageToken); checkSuccess(searchResultProto.getStatus()); - return rewriteSearchResultProto(searchResultProto); + return rewriteSearchResultProto(searchResultProto, mSchemaMapLocked); } finally { mReadWriteLock.readLock().unlock(); } @@ -1104,22 +1117,31 @@ public final class AppSearchImpl implements Closeable { /** * 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 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 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 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 @@ public final class AppSearchImpl implements Closeable { // 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 @@ public final class AppSearchImpl implements Closeable { // 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 @@ public final class AppSearchImpl implements Closeable { } /** - * 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 @@ public final class AppSearchImpl implements Closeable { 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 @@ public final class AppSearchImpl implements Closeable { 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 @@ public final class AppSearchImpl implements Closeable { 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 @@ public final class AppSearchImpl implements Closeable { /** 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 @@ public final class AppSearchImpl implements Closeable { 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 5680670629ae..cdd795207e0b 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 @@ package com.android.server.appsearch.external.localstorage; 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 @@ public final class AppSearchLoggerHelper { 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 d6b9da827515..5ff56abd870a 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 @@ import java.util.Collections; * @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 @@ public final class GenericDocumentToProtoConverter { 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 @@ public final class GenericDocumentToProtoConverter { } 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 800b073fd44f..e3fa7e08aac4 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.annotation.NonNull; 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.StringIndexingConfig; 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 @@ public final class SchemaToProtoConverter { @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 @@ public final class SchemaToProtoConverter { @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 @@ public final class SchemaToProtoConverter { */ @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 @@ public final class SchemaToProtoConverter { @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 bf7e5334d090..57c1590d2bef 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 android.os.Bundle; 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 com.google.android.icing.proto.SnippetProto; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Translates a {@link SearchResultProto} into {@link SearchResult}s. @@ -49,13 +53,17 @@ public class SearchResultToProtoConverter { * @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 @@ public class SearchResultToProtoConverter { 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 @@ public class SearchResultToProtoConverter { * @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 d9e8adb52e5e..8f9e9bdc8cbc 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 @@ package com.android.server.appsearch.external.localstorage.converter; 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 @@ public final class SearchSpecToProtoConverter { /** 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 @@ public final class SearchSpecToProtoConverter { /** 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 @@ public final class SearchSpecToProtoConverter { /** 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 a0f39ecd68fb..ed73593fd1a2 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 @@ package com.android.server.appsearch.external.localstorage.converter; 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 @@ public class SetSchemaResponseToProtoConverter { @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 6f6dad207713..acf04ef08a2c 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 @@ package com.android.server.appsearch.external.localstorage.converter; 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 @@ public final class TypePropertyPathToProtoConverter { @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 a724f95ad8fa..cf640c1a8d21 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 @@ package com.android.server.appsearch.external.localstorage.stats; 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 @@ public class CallStats { 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 @@ public class CallStats { /** 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 8ce8eda3d7a7..53c1ee3f675f 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 @@ package com.android.server.appsearch.external.localstorage.stats; 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 @@ public final class GeneralStats { 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 @@ public final class GeneralStats { * @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 c1f6fb118797..d031172d29c1 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 @@ package com.android.server.appsearch.external.localstorage.stats; 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 @@ public final class PutDocumentStats { 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 @@ public final class PutDocumentStats { /** 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 000000000000..9ae9f1852849 --- /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/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index e46c147e36ff..f99664b4c685 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/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java index 206904372236..494945db5655 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 @@ public interface AppSearchSessionShim extends Closeable { * <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/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 88f3df8fa2c4..9ea6f7946fcf 100644 --- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java +++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java @@ -208,10 +208,10 @@ public class AlarmManager { 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 @@ public class AlarmManager { @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 @@ public class AlarmManager { * 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 @@ public class AlarmManager { * 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 7a3614172dff..31da2017bdef 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 @@ public class AlarmManagerService extends SystemService { 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; @@ -1688,12 +1689,22 @@ public class AlarmManagerService extends SystemService { 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) { + 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 @@ -1737,7 +1748,6 @@ public class AlarmManagerService extends SystemService { // 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) { 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 11a8b3b73075..500735b0b299 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 @@ public final class ConnectivityController extends RestrictingController implemen * 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 @@ public final class ConnectivityController extends RestrictingController implemen * 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 @@ public final class ConnectivityController extends RestrictingController implemen } 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 @@ public final class ConnectivityController extends RestrictingController implemen } 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 @@ public final class ConnectivityController extends RestrictingController implemen 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 @@ public final class ConnectivityController extends RestrictingController implemen 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/core/api/current.txt b/core/api/current.txt index 4b12d54c5a57..04207145673f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8604,37 +8604,37 @@ package android.bluetooth { 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 @@ package android.bluetooth { 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 @@ package android.bluetooth { } 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 public int describeContents(); - method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean fetchUuidsWithSdp(); + 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_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 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"; + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(byte[]); + method public void writeToParcel(android.os.Parcel, int); + 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { } @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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { 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 @@ package android.bluetooth.le { } 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 @@ package android.bluetooth.le { } 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.camera2.params { 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 @@ package android.media { 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 @@ package android.media { 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 @@ package android.media.session { 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 2497827e93f8..b653410aeb8d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -161,6 +161,10 @@ package android.media.session { 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 @@ package android.net { } 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 37bc44f82b32..f7b4cdccee6c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1901,7 +1901,7 @@ package android.bluetooth { 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 @@ package android.bluetooth { 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { } 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 @@ package android.bluetooth { 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 { 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 @@ package android.nfc { 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.printservice.recommendation { 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 b50b8dd3fdb4..bf9f4f1ad37e 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -51,7 +51,7 @@ package android.app.prediction { 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 3080ddf98a54..a77bf32544e4 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3173,5 +3173,12 @@ package android.window { 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 1d89e31b2b99..303ab41bec8e 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.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 static java.lang.annotation.RetentionPolicy.CLASS; import java.lang.annotation.Retention; import java.lang.annotation.Target; @@ -76,7 +76,7 @@ import java.lang.annotation.Target; * * @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 db42803ac9f9..a24555f79a1c 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -4272,7 +4272,8 @@ public class ActivityManager { 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/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8ff14b0ad28f..98fee9cf90cf 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -4390,11 +4390,12 @@ public final class ActivityThread extends ClientTransactionHandler 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/ContextImpl.java b/core/java/android/app/ContextImpl.java index f7ea3815d567..9753b6748c78 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1176,8 +1176,8 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 f9279da172a0..89d90a3b9d6f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -137,7 +137,7 @@ interface IActivityManager { 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 d15d1b72132a..7ce0c7060b05 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -6315,7 +6315,7 @@ public class Notification implements Parcelable * 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 4cf3a8059b3e..ca0868310dee 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -53,7 +53,6 @@ import android.os.RemoteException; 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 @@ public final class PendingIntent implements Parcelable { "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 2ceea7f1a6a8..0ab3f2f4be46 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -861,6 +861,19 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** + * 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 e93138bd1bc8..759597ce36dc 100644 --- a/core/java/android/app/admin/PasswordMetrics.java +++ b/core/java/android/app/admin/PasswordMetrics.java @@ -28,6 +28,7 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 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 @@ public final class PasswordMetrics implements Parcelable { // 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 @@ public final class PasswordMetrics implements Parcelable { /** * 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 @@ public final class PasswordMetrics implements Parcelable { } /** - * 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 @@ public final class PasswordMetrics implements Parcelable { } } + 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 final class PasswordMetrics implements Parcelable { */ 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 @@ public final class PasswordMetrics implements Parcelable { @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 @@ public final class PasswordMetrics implements Parcelable { @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 @@ public final class PasswordMetrics implements Parcelable { 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 @@ public final class PasswordMetrics implements Parcelable { 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 @@ public final class PasswordMetrics implements Parcelable { || !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 13f11ad74d12..0544a3666696 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_ALPHABETIC; 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 android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 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 @@ public class PasswordPolicy { } 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 16413e1a1db6..a268e168ae74 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -23,6 +23,9 @@ import android.annotation.Nullable; 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 @@ public final class BluetoothA2dp implements BluetoothProfile { * <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 @@ public final class BluetoothA2dp implements BluetoothProfile { * * <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 @@ public final class BluetoothA2dp implements BluetoothProfile { * 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 @@ public final class BluetoothA2dp implements BluetoothProfile { * 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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); try { @@ -387,6 +396,8 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); try { @@ -406,6 +417,8 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * 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 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean shouldSendVolumeKeys(BluetoothDevice device) { if (isEnabled() && isValidDevice(device)) { ParcelUuid[] uuids = device.getUuids(); @@ -686,7 +715,9 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * 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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 @@ public final class BluetoothA2dp implements BluetoothProfile { * @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 67f3d7b5d717..d81316e357d3 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -21,6 +21,9 @@ import android.annotation.NonNull; 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 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @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 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * 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 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * * @hide */ + @RequiresLegacyBluetoothPermission public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { if (VDBG) log("getAudioConfig(" + device + ")"); final IBluetoothA2dpSink service = getService(); @@ -278,7 +282,10 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @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 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @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 79fd8072f9f0..972e9e6d73b0 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.Nullable; 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 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * 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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * {@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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE() { if (!isBleScanAlwaysAvailable()) { return false; @@ -966,6 +983,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE() { if (!isBleScanAlwaysAvailable()) { return false; @@ -986,6 +1004,7 @@ public final class BluetoothAdapter { 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 @@ public final class BluetoothAdapter { * * @return current state of Bluetooth adapter */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission @AdapterState public int getState() { int state = getStateInternal(); @@ -1075,7 +1094,7 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { /** * 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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { /** * 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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { */ @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { /** @hide */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getDiscoverableTimeout() { if (getState() != STATE_ON) { return -1; @@ -1610,6 +1657,7 @@ public final class BluetoothAdapter { /** @hide */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void setDiscoverableTimeout(int timeout) { if (getState() != STATE_ON) { return; @@ -1635,7 +1683,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public long getDiscoveryEndMillis() { try { mServiceLock.readLock().lock(); @@ -1703,7 +1751,10 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @return true if Multiple Advertisement feature is supported */ + @RequiresLegacyBluetoothPermission public boolean isMultipleAdvertisementSupported() { if (getState() != STATE_ON) { return false; @@ -1981,6 +2052,7 @@ public final class BluetoothAdapter { 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 @@ public final class BluetoothAdapter { * * @return true if chipset supports on-chip filtering */ + @RequiresLegacyBluetoothPermission public boolean isOffloadedFilteringSupported() { if (!getLeAccess()) { return false; @@ -2024,6 +2097,7 @@ public final class BluetoothAdapter { * * @return true if chipset supports on-chip scan batching */ + @RequiresLegacyBluetoothPermission public boolean isOffloadedScanBatchingSupported() { if (!getLeAccess()) { return false; @@ -2046,6 +2120,7 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE 2M PHY feature */ + @RequiresLegacyBluetoothPermission public boolean isLe2MPhySupported() { if (!getLeAccess()) { return false; @@ -2068,6 +2143,7 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Coded PHY feature */ + @RequiresLegacyBluetoothPermission public boolean isLeCodedPhySupported() { if (!getLeAccess()) { return false; @@ -2090,6 +2166,7 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Extended Advertising feature */ + @RequiresLegacyBluetoothPermission public boolean isLeExtendedAdvertisingSupported() { if (!getLeAccess()) { return false; @@ -2112,6 +2189,7 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Periodic Advertising feature */ + @RequiresLegacyBluetoothPermission public boolean isLePeriodicAdvertisingSupported() { if (!getLeAccess()) { return false; @@ -2135,6 +2213,7 @@ public final class BluetoothAdapter { * * @return the maximum LE advertising data length. */ + @RequiresLegacyBluetoothPermission public int getLeMaximumAdvertisingDataLength() { if (!getLeAccess()) { return 0; @@ -2172,7 +2251,9 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * instead. */ @Deprecated + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) { SynchronousResultReceiver receiver = new SynchronousResultReceiver(); requestControllerActivityEnergyInfo(receiver); @@ -2248,6 +2331,7 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage + @RequiresLegacyBluetoothPermission public int getConnectionState() { if (getState() != STATE_ON) { return BluetoothAdapter.STATE_DISCONNECTED; @@ -2429,6 +2519,7 @@ public final class BluetoothAdapter { 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 @@ public final class BluetoothAdapter { * {@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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * <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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { /** * 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 @@ public final class BluetoothAdapter { * @param profile * @param proxy Profile proxy object */ + @SuppressLint("AndroidFrameworkRequiresPermission") public void closeProfileProxy(int profile, BluetoothProfile proxy) { if (proxy == null) { return; @@ -2937,6 +3048,7 @@ public final class BluetoothAdapter { 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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * * @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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { /** * @hide */ + @SuppressLint("AndroidFrameworkRequiresPermission") public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub { private BluetoothStateChangeCallback mCallback; @@ -3447,7 +3560,10 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * 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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 @@ public final class BluetoothAdapter { * @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 4e7e4415c54d..887cf3f08b9d 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 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * <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 0c208fd71aed..1201663d1d10 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.SdkConstant.SdkConstantType; 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 @@ import java.util.UUID; * {@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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { 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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * {@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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { /** * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { /** * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * <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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { */ @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 @@ public final class BluetoothDevice implements Parcelable { /** * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * 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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { * @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 @@ public final class BluetoothDevice implements Parcelable { */ @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 381318b26dad..942f8432639c 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 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onClientRegistered(int status, int clientIf) { if (DBG) { Log.d(TAG, "onClientRegistered() - status=" + status @@ -347,6 +356,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onCharacteristicRead(String address, int status, int handle, byte[] value) { if (VDBG) { @@ -404,6 +414,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @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 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onDescriptorRead(String address, int status, int handle, byte[] value) { if (VDBG) { Log.d(TAG, @@ -538,6 +550,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onDescriptorWrite(String address, int status, int handle) { if (VDBG) { Log.d(TAG, @@ -734,6 +747,7 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { /** * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * * @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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * {@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 @@ public final class BluetoothGatt implements BluetoothProfile { * {@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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * @deprecated Use {@link #abortReliableWrite()} */ @Deprecated + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite(BluetoothDevice mDevice) { abortReliableWrite(); } @@ -1426,12 +1457,13 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * @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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * <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 @@ public final class BluetoothGatt implements BluetoothProfile { * 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 @@ public final class BluetoothGatt implements BluetoothProfile { * @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 8f1b59cf69e6..8a7d4baf5add 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -237,7 +237,6 @@ public class BluetoothGattCharacteristic implements Parcelable { /** * 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 @@ public class BluetoothGattCharacteristic implements Parcelable { /** * 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 49ba281e2eb7..ed5ea0873020 100644 --- a/core/java/android/bluetooth/BluetoothGattDescriptor.java +++ b/core/java/android/bluetooth/BluetoothGattDescriptor.java @@ -128,7 +128,6 @@ public class BluetoothGattDescriptor implements Parcelable { /** * 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 @@ public class BluetoothGattDescriptor implements Parcelable { /** * 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 @@ public class BluetoothGattDescriptor implements Parcelable { * <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 088b0169b631..fdb801850e8e 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * <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 @@ public final class BluetoothGattServer implements BluetoothProfile { * <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 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * * @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 @@ public final class BluetoothGattServer implements BluetoothProfile { * <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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * @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 @@ public final class BluetoothGattServer implements BluetoothProfile { * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * 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 @@ public final class BluetoothGattServer implements BluetoothProfile { * <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 @@ public final class BluetoothGattServer implements BluetoothProfile { * <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 23dc7c830855..f64d09fc30d9 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 @@ public class BluetoothGattService implements Parcelable { /** * 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 @@ public class BluetoothGattService implements Parcelable { /** * 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 @@ public class BluetoothGattService implements Parcelable { /** * 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 632572dea3c6..84e8c5134e7b 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -22,6 +22,9 @@ import android.annotation.Nullable; 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * <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 @@ public final class BluetoothHeadset implements BluetoothProfile { * </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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * <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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@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 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHeadset service = mService; @@ -521,6 +529,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@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 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@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 @@ public final class BluetoothHeadset implements BluetoothProfile { */ @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @hide */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getAudioRouteAllowed() { if (VDBG) log("getAudioRouteAllowed"); final IBluetoothHeadset service = mService; @@ -897,6 +931,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { @@ -982,6 +1019,7 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { @@ -1018,7 +1056,12 @@ public final class BluetoothHeadset implements BluetoothProfile { * - 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * - 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * * <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 @@ public final class BluetoothHeadset implements BluetoothProfile { * 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 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@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 @@ public final class BluetoothHeadset implements BluetoothProfile { */ @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 @@ public final class BluetoothHeadset implements BluetoothProfile { * @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 e5b2a1e23cc1..092130d0ce91 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -19,6 +19,8 @@ package android.bluetooth; 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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * 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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * 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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * 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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * 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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @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 5fd60e001693..65f68a943e08 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 @@ public final class BluetoothHealth implements BluetoothProfile { * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * {@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 @@ public final class BluetoothHealth implements BluetoothProfile { * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * {@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 @@ public final class BluetoothHealth implements BluetoothProfile { * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * {@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 @@ public final class BluetoothHealth implements BluetoothProfile { * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * {@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 @@ public final class BluetoothHealth implements BluetoothProfile { * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * {@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 @@ public final class BluetoothHealth implements BluetoothProfile { /** * 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 @@ public final class BluetoothHealth implements BluetoothProfile { * #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 @@ public final class BluetoothHealth implements BluetoothProfile { * * <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 @@ public final class BluetoothHealth implements BluetoothProfile { * @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 @@ public final class BluetoothHealth implements BluetoothProfile { * <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 @@ public final class BluetoothHealth implements BluetoothProfile { * @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 ff78825e0f96..8ceeff53b130 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -22,6 +22,9 @@ import android.annotation.Nullable; 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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * <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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * 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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( @NonNull int[] states) { if (VDBG) log("getDevicesMatchingStates()"); @@ -264,6 +272,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @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 2baa73822c9c..c214d2b85ac5 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -21,6 +21,8 @@ import android.annotation.NonNull; 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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * <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 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { final IBluetoothHidDevice service = getService(); if (service != null) { @@ -453,6 +457,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { final IBluetoothHidDevice service = getService(); if (service != null) { @@ -470,6 +475,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { final IBluetoothHidDevice service = getService(); if (service != null) { @@ -508,6 +514,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { * 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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @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 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @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 9561d9383846..70e3809cb590 100644 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ b/core/java/android/bluetooth/BluetoothHidHost.java @@ -21,6 +21,9 @@ import android.annotation.NonNull; 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 @@ public final class BluetoothHidHost implements BluetoothProfile { * <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 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @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 @@ public final class BluetoothHidHost implements BluetoothProfile { * @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 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { * @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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * 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 3f00fa6f4181..4f095f6c7001 100644 --- a/core/java/android/bluetooth/BluetoothLeAudio.java +++ b/core/java/android/bluetooth/BluetoothLeAudio.java @@ -23,6 +23,9 @@ import android.annotation.Nullable; 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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * <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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * 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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { /** * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public void close() { mProfileConnector.disconnect(); } @@ -131,7 +133,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { return mProfileConnector.getService(); } - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize() { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); @@ -154,7 +155,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@inheritDoc} */ @Override + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); try { @@ -232,6 +234,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@inheritDoc} */ @Override + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( @NonNull int[] states) { if (VDBG) log("getDevicesMatchingStates()"); @@ -252,7 +255,9 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @hide */ @NonNull - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission public List<BluetoothDevice> getActiveDevices() { if (VDBG) log("getActiveDevices()"); try { @@ -337,7 +342,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @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 d5c1c3e2d61e..a1e1b6305083 100644 --- a/core/java/android/bluetooth/BluetoothManager.java +++ b/core/java/android/bluetooth/BluetoothManager.java @@ -20,6 +20,8 @@ import android.Manifest; 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 @@ public final class BluetoothManager { * {@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 @@ public final class BluetoothManager { * @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 @@ public final class BluetoothManager { * {@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 @@ public final class BluetoothManager { * @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 @@ public final class BluetoothManager { * @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 @@ public final class BluetoothManager { * @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 @@ public final class BluetoothManager { * @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 35549954007e..3e7b75aa1f62 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -93,7 +93,6 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { mCloseGuard.open("close"); } - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize() { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); @@ -110,7 +109,6 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public void close() { if (VDBG) log("close()"); mProfileConnector.disconnect(); @@ -128,6 +126,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getState() { if (VDBG) log("getState()"); final IBluetoothMap service = getService(); @@ -152,6 +151,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothDevice getClient() { if (VDBG) log("getClient()"); final IBluetoothMap service = getService(); @@ -175,6 +175,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); final IBluetoothMap service = getService(); @@ -302,6 +308,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); final IBluetoothMap service = getService(); @@ -328,7 +335,10 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @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 0312a2190a4b..db74a90f603b 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -192,6 +192,7 @@ public final class BluetoothMapClient implements BluetoothProfile { * 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 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 @@ public final class BluetoothMapClient implements BluetoothProfile { * 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 @@ public final class BluetoothMapClient implements BluetoothProfile { * @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 ecd718cec32b..b3924b1fa920 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -22,6 +22,8 @@ import android.annotation.NonNull; 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 @@ public final class BluetoothPan implements BluetoothProfile { * * <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 @@ public final class BluetoothPan implements BluetoothProfile { * * <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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 @@ public final class BluetoothPan implements BluetoothProfile { */ @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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 @@ public final class BluetoothPan implements BluetoothProfile { * @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 6e5c45f3d129..6c2e5bf2d391 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -22,6 +22,8 @@ import android.annotation.RequiresPermission; 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 @@ public class BluetoothPbap implements BluetoothProfile { * 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 @@ public class BluetoothPbap implements BluetoothProfile { doBind(); } + @SuppressLint("AndroidFrameworkRequiresPermission") boolean doBind() { synchronized (mConnection) { try { @@ -216,6 +217,7 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { log("getConnectedDevices()"); final IBluetoothPbap service = mService; @@ -262,6 +264,7 @@ public class BluetoothPbap implements BluetoothProfile { * @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 @@ public class BluetoothPbap implements BluetoothProfile { * @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 @@ public class BluetoothPbap implements BluetoothProfile { * @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 f356da18fc73..2c8fbc2509ff 100644 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ b/core/java/android/bluetooth/BluetoothPbapClient.java @@ -19,6 +19,7 @@ package android.bluetooth; 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 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return list of connected devices */ @Override + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (DBG) { log("getConnectedDevices()"); @@ -185,6 +187,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @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 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @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 201d6c495d98..70053ee71491 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 @@ public interface BluetoothProfile { * * @return List of devices. The list will be empty on error. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) public List<BluetoothDevice> getConnectedDevices(); /** @@ -314,7 +310,6 @@ public interface BluetoothProfile { * #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 @@ public interface BluetoothProfile { * @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 863fd3698cbd..12abcc4d11da 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 @@ public abstract class BluetoothProfileConnector<T> { 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 0d70dbdd8427..c85494c01b25 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -18,6 +18,9 @@ package android.bluetooth; 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 @@ public final class BluetoothSap implements BluetoothProfile { * {@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 @@ public final class BluetoothSap implements BluetoothProfile { * 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 @@ public final class BluetoothSap implements BluetoothProfile { * 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 @@ public final class BluetoothSap implements BluetoothProfile { * * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 @@ public final class BluetoothSap implements BluetoothProfile { * @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 5c1bcaf31319..50822354d69f 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -62,9 +62,6 @@ import java.io.IOException; * 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 65381dbb2372..ef88147a40fb 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 @@ import java.util.UUID; * 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 @@ public final class BluetoothSocket implements Closeable { * @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 @@ public final class BluetoothSocket implements Closeable { * * @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 @@ public final class BluetoothSocket implements Closeable { * 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 @@ public final class BluetoothSocket implements Closeable { * 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 08d694eb93e2..d6868e0ffd5c 100644 --- a/core/java/android/bluetooth/OobData.java +++ b/core/java/android/bluetooth/OobData.java @@ -830,7 +830,7 @@ public final class OobData implements Parcelable { @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 000000000000..c508c2c9ca0b --- /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 000000000000..e159eaafe2e4 --- /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 000000000000..2bb320413941 --- /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 000000000000..800ff39933f2 --- /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 000000000000..9adf695cde0f --- /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 000000000000..79621c366f59 --- /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 1df35e1e382f..54a18e6f1d62 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 @@ import android.util.Log; * <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 @@ public final class AdvertisingSet { /** * 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 @@ public final class AdvertisingSet { * 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 @@ public final class AdvertisingSet { * 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 @@ public final class AdvertisingSet { * 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 @@ public final class AdvertisingSet { * * @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 @@ public final class AdvertisingSet { * 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 @@ public final class AdvertisingSet { * 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 @@ public final class AdvertisingSet { * @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 @@ public final class AdvertisingSet { * 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 5f166f4a41da..de11869e220e 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 @@ import java.util.Map; * <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 @@ public final class BluetoothLeAdvertiser { /** * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { } } + @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 @@ public final class BluetoothLeAdvertiser { /* Legacy advertiser is disabled on timeout */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, int status) { if (enabled) { @@ -192,11 +204,12 @@ public final class BluetoothLeAdvertiser { /** * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { * 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 @@ public final class BluetoothLeAdvertiser { } // 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 2601cd4300ea..4271a905c220 100644 --- a/core/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java @@ -20,12 +20,16 @@ import android.Manifest; 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 @@ import java.util.Map; * <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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * 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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * * @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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { * @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 @@ public final class BluetoothLeScanner { mResultStorages = resultStorages; } + @SuppressLint("AndroidFrameworkRequiresPermission") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startRegistration() { synchronized (this) { // Scan stopped. @@ -409,6 +441,8 @@ public final class BluetoothLeScanner { } } + @SuppressLint("AndroidFrameworkRequiresPermission") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan() { synchronized (this) { if (mScannerId <= 0) { @@ -425,6 +459,8 @@ public final class BluetoothLeScanner { } } + @SuppressLint("AndroidFrameworkRequiresPermission") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) void flushPendingBatchResults() { synchronized (this) { if (mScannerId <= 0) { @@ -443,6 +479,7 @@ public final class BluetoothLeScanner { * 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 @@ public final class BluetoothLeScanner { 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 0f1a8e913ba8..9ea6c4866f6d 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 @@ import java.util.Map; * <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 @@ public final class PeriodicAdvertisingManager { * @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 @@ public final class PeriodicAdvertisingManager { * @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 @@ public final class PeriodicAdvertisingManager { * @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 09ac8103c526..318913f3bec0 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2208,6 +2208,17 @@ public abstract class Context { } /** + * 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 8936d0c47a58..dddcbea63872 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -493,6 +493,13 @@ public class ContextWrapper extends Context { /** @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 5ff11240db72..86a8a9d69782 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8923,6 +8923,8 @@ public class PackageParser { 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 @@ public class PackageParser { 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 class PackageParser { public void close() throws Exception { IoUtils.closeQuietly(mCachedAssetManager); } + + @Override + public ApkAssets getBaseApkAssets() { + return mBaseApkAssets; + } } /** @@ -9085,5 +9094,10 @@ public class PackageParser { 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 a1ffc0ca5378..c2ca384751dc 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -383,10 +383,9 @@ public class ParsingPackageUtils { } 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 @@ public class ParsingPackageUtils { 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 @@ public class ParsingPackageUtils { } 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 @@ public class ParsingPackageUtils { 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 @@ public class ParsingPackageUtils { } } - 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()) { diff --git a/core/java/android/content/pm/parsing/component/ParsedAttribution.java b/core/java/android/content/pm/parsing/component/ParsedAttribution.java index 3a4aae19d459..4ec2e73a0b83 100644 --- a/core/java/android/content/pm/parsing/component/ParsedAttribution.java +++ b/core/java/android/content/pm/parsing/component/ParsedAttribution.java @@ -40,7 +40,7 @@ public class ParsedAttribution implements Parcelable { public static final int MAX_ATTRIBUTION_TAG_LEN = 50; /** Maximum amount of attributions per package */ - private static final int MAX_NUM_ATTRIBUTIONS = 1000; + private static final int MAX_NUM_ATTRIBUTIONS = 10000; /** Tag of the attribution */ public final @NonNull String tag; @@ -100,7 +100,7 @@ public class ParsedAttribution implements Parcelable { - // Code below generated by codegen v1.0.22. + // Code below generated by codegen v1.0.23. // // DO NOT MODIFY! // CHECKSTYLE:OFF Generated code @@ -215,8 +215,8 @@ public class ParsedAttribution implements Parcelable { }; @DataClass.Generated( - time = 1607463855175L, - codegenVersion = "1.0.22", + time = 1618351459610L, + codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttribution.java", inputSignatures = "public static final int MAX_ATTRIBUTION_TAG_LEN\nprivate static final int MAX_NUM_ATTRIBUTIONS\npublic final @android.annotation.NonNull java.lang.String tag\npublic final @android.annotation.StringRes int label\npublic final @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\npublic static boolean isCombinationValid(java.util.List<android.content.pm.parsing.component.ParsedAttribution>)\nclass ParsedAttribution extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false)") @Deprecated diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index f3caf603921f..c1a83967dea1 100644 --- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java +++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java @@ -43,6 +43,8 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { 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 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { // 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 @@ public class DefaultSplitAssetLoader implements SplitAssetLoader { } @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 523ca405eec7..e5c2158fcd38 100644 --- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java +++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java @@ -128,6 +128,11 @@ public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackagePar } @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 108fb95a150d..7584e15fb6d8 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 @@ import android.content.res.AssetManager; 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 adc668f8fa02..77bd14756637 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 android.os.UserHandle; 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 @@ public final class DomainVerificationManager { * * 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 @@ public final class DomainVerificationManager { @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 3c11d8ed97b1..bc2dcb3b4e62 100644 --- a/core/java/android/hardware/SystemSensorManager.java +++ b/core/java/android/hardware/SystemSensorManager.java @@ -571,10 +571,10 @@ public class SystemSensorManager extends SensorManager { } 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 @@ public class SystemSensorManager extends SensorManager { 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 62d727c080e3..1268658d75f5 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 11c426aa6734..835b3fd2dfe5 100644 --- a/core/java/android/hardware/display/DeviceProductInfo.java +++ b/core/java/android/hardware/display/DeviceProductInfo.java @@ -74,12 +74,26 @@ public final class DeviceProductInfo implements Parcelable { 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 @@ public final class DeviceProductInfo implements Parcelable { } /** + * 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 5f65d46f3b1e..662ebb356f4c 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -78,10 +78,8 @@ public class VpnManager { /** * 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 @@ public class VpnManager { 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 d5cc01aac0cc..cb9a3e43db81 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -24,7 +24,7 @@ import android.nfc.Tag; 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 @@ interface INfcAdapter 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 index 1e4fdd79e831..1bb7680d2fed 100644 --- a/core/java/android/nfc/INfcControllerAlwaysOnStateCallback.aidl +++ b/core/java/android/nfc/INfcControllerAlwaysOnListener.aidl @@ -19,11 +19,11 @@ package android.nfc; /** * @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 bbf802ca58d8..64c121194932 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -67,7 +67,7 @@ import java.util.concurrent.Executor; 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 @@ public final class NfcAdapter { } /** - * 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 @@ public final class NfcAdapter { mNfcUnlockHandlers = new HashMap<NfcUnlockHandler, INfcUnlockHandler>(); mTagRemovedListener = null; mLock = new Object(); - mControllerAlwaysOnStateListener = new NfcControllerAlwaysOnStateListener(getService()); + mControllerAlwaysOnListener = new NfcControllerAlwaysOnListener(getService()); } /** @@ -2246,12 +2246,12 @@ public final class NfcAdapter { * <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 @@ public final class NfcAdapter { } /** - * 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/NfcControllerAlwaysOnStateListener.java b/core/java/android/nfc/NfcControllerAlwaysOnListener.java index 69a9ec79edb2..96707bb432db 100644 --- a/core/java/android/nfc/NfcControllerAlwaysOnStateListener.java +++ b/core/java/android/nfc/NfcControllerAlwaysOnListener.java @@ -17,7 +17,7 @@ package android.nfc; import android.annotation.NonNull; -import android.nfc.NfcAdapter.ControllerAlwaysOnStateCallback; +import android.nfc.NfcAdapter.ControllerAlwaysOnListener; import android.os.Binder; import android.os.RemoteException; import android.util.Log; @@ -29,77 +29,77 @@ import java.util.concurrent.Executor; /** * @hide */ -public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnStateCallback.Stub { - private static final String TAG = "NfcControllerAlwaysOnStateListener"; +public class NfcControllerAlwaysOnListener extends INfcControllerAlwaysOnListener.Stub { + private static final String TAG = NfcControllerAlwaysOnListener.class.getSimpleName(); private final INfcAdapter mAdapter; - private final Map<ControllerAlwaysOnStateCallback, Executor> mCallbackMap = new HashMap<>(); + private final Map<ControllerAlwaysOnListener, Executor> mListenerMap = new HashMap<>(); private boolean mCurrentState = false; private boolean mIsRegistered = false; - public NfcControllerAlwaysOnStateListener(@NonNull INfcAdapter adapter) { + public NfcControllerAlwaysOnListener(@NonNull INfcAdapter adapter) { mAdapter = adapter; } /** - * Register a {@link ControllerAlwaysOnStateCallback} with this - * {@link NfcControllerAlwaysOnStateListener} + * Register a {@link ControllerAlwaysOnListener} with this + * {@link NfcControllerAlwaysOnListener} * - * @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} */ public void register(@NonNull Executor executor, - @NonNull ControllerAlwaysOnStateCallback callback) { + @NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - if (mCallbackMap.containsKey(callback)) { + if (mListenerMap.containsKey(listener)) { return; } - mCallbackMap.put(callback, executor); + mListenerMap.put(listener, executor); if (!mIsRegistered) { try { - mAdapter.registerControllerAlwaysOnStateCallback(this); + mAdapter.registerControllerAlwaysOnListener(this); mIsRegistered = true; } catch (RemoteException e) { - Log.w(TAG, "Failed to register ControllerAlwaysOnStateListener"); + Log.w(TAG, "Failed to register"); } } } } /** - * Unregister the specified {@link ControllerAlwaysOnStateCallback} + * Unregister the specified {@link ControllerAlwaysOnListener} * - * @param callback user implementation of the {@link ControllerAlwaysOnStateCallback} + * @param listener user implementation of the {@link ControllerAlwaysOnListener} */ - public void unregister(@NonNull ControllerAlwaysOnStateCallback callback) { + public void unregister(@NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - if (!mCallbackMap.containsKey(callback)) { + if (!mListenerMap.containsKey(listener)) { return; } - mCallbackMap.remove(callback); + mListenerMap.remove(listener); - if (mCallbackMap.isEmpty() && mIsRegistered) { + if (mListenerMap.isEmpty() && mIsRegistered) { try { - mAdapter.unregisterControllerAlwaysOnStateCallback(this); + mAdapter.unregisterControllerAlwaysOnListener(this); } catch (RemoteException e) { - Log.w(TAG, "Failed to unregister ControllerAlwaysOnStateListener"); + Log.w(TAG, "Failed to unregister"); } mIsRegistered = false; } } } - private void sendCurrentState(@NonNull ControllerAlwaysOnStateCallback callback) { + private void sendCurrentState(@NonNull ControllerAlwaysOnListener listener) { synchronized (this) { - Executor executor = mCallbackMap.get(callback); + Executor executor = mListenerMap.get(listener); final long identity = Binder.clearCallingIdentity(); try { - executor.execute(() -> callback.onStateChanged( + executor.execute(() -> listener.onControllerAlwaysOnChanged( mCurrentState)); } finally { Binder.restoreCallingIdentity(identity); @@ -108,10 +108,10 @@ public class NfcControllerAlwaysOnStateListener extends INfcControllerAlwaysOnSt } @Override - public void onControllerAlwaysOnStateChanged(boolean isEnabled) { + public void onControllerAlwaysOnChanged(boolean isEnabled) { synchronized (this) { mCurrentState = isEnabled; - for (ControllerAlwaysOnStateCallback cb : mCallbackMap.keySet()) { + for (ControllerAlwaysOnListener cb : mListenerMap.keySet()) { sendCurrentState(cb); } } diff --git a/core/java/android/nfc/TEST_MAPPING b/core/java/android/nfc/TEST_MAPPING new file mode 100644 index 000000000000..71ad687b7889 --- /dev/null +++ b/core/java/android/nfc/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "NfcManagerTests" + } + ] +} diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 9bfd75ef2170..51f19ebee987 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -22,10 +22,10 @@ import android.annotation.CallbackExecutor; 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 class CallLog { 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 @@ public class CallLog { * 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 @@ public class CallLog { * @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 @@ public class CallLog { 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 @@ public class CallLog { } /** - * @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 9450994af89f..2616a6676db1 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9136,6 +9136,20 @@ public final class Settings { "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/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 2b96a14b04d4..7bdf5cf879f3 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -30,6 +30,7 @@ import android.graphics.BLASTBufferQueue; 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall 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 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * 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/window/WindowContextController.java b/core/java/android/window/WindowContextController.java index 88584f4b2571..d84f571931fd 100644 --- a/core/java/android/window/WindowContextController.java +++ b/core/java/android/window/WindowContextController.java @@ -105,6 +105,7 @@ public class WindowContextController { * 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 000000000000..b8619fbcf334 --- /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/util/State.java b/core/java/com/android/internal/util/State.java index 4613dad8cc67..d5c0f60f4b37 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 @@ import android.os.Message; * * The class for implementing states in a StateMachine */ +@SuppressLint("AndroidFrameworkRequiresPermission") public class State implements IState { /** diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index c51b2d84ab6d..588ae79cda1d 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 906a7403298f..419f142d0795 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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 34dcaa0c78b2..d69c7f8f9587 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" /> 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 000000000000..1f5731821c94 --- /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 000000000000..70aac32227c4 --- /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 000000000000..39f9689d2ec4 --- /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 000000000000..9cae545e165b --- /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 1ced825adf31..98fc581f3420 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 e58a08fd362c..24d193c49219 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/packages/SystemUI/res/values-h740dp-port/dimens.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml index 966066ffe56b..2dbe48b6edc0 100644 --- a/packages/SystemUI/res/values-h740dp-port/dimens.xml +++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml @@ -1,5 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2020 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. @@ -11,17 +12,10 @@ ~ 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 + ~ 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 + <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 8df3de3db668..f7d7098b1726 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 @@ package com.android.frameworks.core.batterystatsviewer; 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,260 +41,155 @@ public class BatteryConsumerData { 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(); - - 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; - - 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(); - } + BatteryUsageStats modeledBatteryUsageStats = batteryUsageStatsList.get(1); - if (batteryConsumerId(sipper).equals(batteryConsumerId)) { - requestedBatterySipper = sipper; - } + BatteryConsumer requestedBatteryConsumer = getRequestedBatteryConsumer(batteryUsageStats, + batteryConsumerId); + BatteryConsumer requestedModeledBatteryConsumer = getRequestedBatteryConsumer( + modeledBatteryUsageStats, batteryConsumerId); - totalPowerMah += sipper.sumPower(); - totalSmearedPowerMah += sipper.totalSmearedPowerMah; - totalProportionalSmearMah += sipper.proportionalSmearMah; + if (requestedBatteryConsumer == null || requestedModeledBatteryConsumer == null) { + mBatteryConsumerInfo = null; + return; + } - if (!isSystemSipper(sipper)) { - totalPowerExcludeSystemMah += sipper.totalSmearedPowerMah; + mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo( + context.getPackageManager(), requestedBatteryConsumer); + + 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]; + + computeTotalPower(batteryUsageStats, totalPowerByComponentMah); + computeTotalPower(modeledBatteryUsageStats, totalModeledPowerByComponentMah); + computeTotalPowerForCustomComponent(batteryUsageStats, totalCustomPowerByComponentMah); + computeTotalDuration(batteryUsageStats, totalDurationByComponentMs); + + 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); } + } - 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; + 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); } - BatteryConsumer requestedBatteryConsumer = null; + 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 BatteryConsumer getRequestedBatteryConsumer(BatteryUsageStats batteryUsageStats, + String batteryConsumerId) { for (BatteryConsumer consumer : batteryUsageStats.getUidBatteryConsumers()) { if (batteryConsumerId(consumer).equals(batteryConsumerId)) { - requestedBatteryConsumer = consumer; + return consumer; } } - - double totalModeledCpuPowerMah = 0; - BatteryConsumer requestedBatteryConsumerPowerProfileModeled = null; - for (BatteryConsumer consumer : powerProfileModeledUsageStats.getUidBatteryConsumers()) { + for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) { if (batteryConsumerId(consumer).equals(batteryConsumerId)) { - requestedBatteryConsumerPowerProfileModeled = consumer; + return consumer; } - - totalModeledCpuPowerMah += consumer.getConsumedPower( - BatteryConsumer.POWER_COMPONENT_CPU); } + return null; + } - if (requestedBatterySipper == null) { - mBatteryConsumerInfo = null; - return; - } + 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"; + } - if (requestedBatteryConsumer == null) { - for (BatteryConsumer consumer : batteryUsageStats.getSystemBatteryConsumers()) { - if (batteryConsumerId(consumer).equals(batteryConsumerId)) { - requestedBatteryConsumer = consumer; - break; - } + 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); } } + } - mBatteryConsumerInfo = BatteryConsumerInfoHelper.makeBatteryConsumerInfo( - context.getPackageManager(), requestedBatterySipper); - long totalScreenMeasuredChargeUC = - batteryStats.getScreenOnMeasuredBatteryConsumptionUC(); - long uidScreenMeasuredChargeUC = - requestedBatterySipper.uidObj.getScreenOnMeasuredBatteryConsumptionUC(); - - addEntry("Total power", EntryType.POWER, - requestedBatterySipper.totalSmearedPowerMah, totalSmearedPowerMah); - maybeAddMeasuredEnergyEntry(requestedBatterySipper.drainType, batteryStats); - - 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); + 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); } - } else { - addEntry("CPU (sipper)", EntryType.POWER, - requestedBatterySipper.cpuPowerMah, totalCpuPowerMah); } - addEntry("System services", EntryType.POWER, - requestedBatterySipper.systemServiceCpuPowerMah, totalSystemServiceCpuPowerMah); - 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); - - 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; - } - } + 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); } } - - return false; } - private void addEntry(String title, EntryType entryType, double amount, double totalAmount) { + 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; } @@ -314,13 +198,9 @@ public class BatteryConsumerData { 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 8ee6c604cb3e..6288e0b886d1 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 @@ package com.android.frameworks.core.batterystatsviewer; 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 @@ class BatteryConsumerInfoHelper { 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 (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; } } + } - 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 (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); + 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; } - for (String packageName : packages) { - if (packageName.equals(primaryPackageName)) { - continue; - } - if (sb.length() != 0) { - sb.append(", "); - } - sb.append(packageName); + if (sb.length() != 0) { + sb.append(", "); } - - info.packages = sb; + sb.append(packageName); } + + info.packages = sb; } - break; } - 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 bb11fd598511..49220877d31e 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 @@ package com.android.frameworks.core.batterystatsviewer; 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.LinearLayoutManager; 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 @@ public class BatteryConsumerPickerFragment extends Fragment { 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() { - List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>(); + final BatteryUsageStats batteryUsageStats = mBatteryStatsManager.getBatteryUsageStats(); - 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)); + List<BatteryConsumerInfo> batteryConsumerList = new ArrayList<>(); + 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 4ead8eef5684..74d3fb336f40 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 @@ package com.android.frameworks.core.batterystatsviewer; 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.loader.content.Loader; 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 @@ import java.util.Locale; 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { @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 class BatteryStatsViewerActivity extends ComponentActivity { } } - 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { } 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { 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 @@ public class BatteryStatsViewerActivity extends ComponentActivity { 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 class BatteryStatsViewerActivity extends ComponentActivity { 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 DURATION: + case POWER_MEASURED: viewHolder.titleTextView.setText(entry.title); viewHolder.amountTextView.setText( - String.format(Locale.getDefault(), "%,d ms", (long) entry.value)); + 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); + 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 fb0dd46a52f0..b2b9ab31282c 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 @@ public class PasswordMetricsTest { @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 @@ public class PasswordMetricsTest { @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 e951054e6558..f1be173b3677 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 android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED 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 class PasswordPolicyTest { 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 class PasswordPolicyTest { 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 56c685afa026..3d18337ab257 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 com.google.common.truth.Truth.assertThat; 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 @@ public class AppSearchSessionUnitTest { }); // 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 000000000000..de0670b08ffd --- /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/window/WindowContextTest.java b/core/tests/coretests/src/android/window/WindowContextTest.java index 614e7c1d6fa4..83280f18c889 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 @@ public class WindowContextTest { 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 @@ public class WindowContextTest { .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/nfctests/Android.bp b/core/tests/nfctests/Android.bp new file mode 100644 index 000000000000..335cea140df6 --- /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 000000000000..99e2c34c656b --- /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 000000000000..490d6f5df197 --- /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 000000000000..34b095c7fda0 --- /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 000000000000..43f9b6feea45 --- /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 e85cc8d87fc5..5f344267d71c 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -406,6 +406,7 @@ applications that come with the platform <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 000000000000..2d3456b0ea46 --- /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 000000000000..3b5a58c46f71 --- /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 000000000000..771258d7d265 --- /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 000000000000..670eb3b619ce --- /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 000000000000..4150c478cc69 --- /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/errorprone/tests/res/android/content/BroadcastReceiver.java b/errorprone/tests/res/android/content/BroadcastReceiver.java new file mode 100644 index 000000000000..9d066b768015 --- /dev/null +++ b/errorprone/tests/res/android/content/BroadcastReceiver.java @@ -0,0 +1,23 @@ +/* + * 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.content; + +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 7ba3fbb56245..323b8dd46e8f 100644 --- a/errorprone/tests/res/android/content/Context.java +++ b/errorprone/tests/res/android/content/Context.java @@ -20,4 +20,7 @@ public class Context { public int getUserId() { return 0; } + + public void enforceCallingOrSelfPermission(String permission, String message) { + } } diff --git a/errorprone/tests/res/android/database/ContentObserver.java b/errorprone/tests/res/android/database/ContentObserver.java new file mode 100644 index 000000000000..4c73a10cad26 --- /dev/null +++ b/errorprone/tests/res/android/database/ContentObserver.java @@ -0,0 +1,23 @@ +/* + * 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.database; + +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 000000000000..20c8e95832e0 --- /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/errorprone/tests/res/android/os/Handler.java b/errorprone/tests/res/android/os/Handler.java new file mode 100644 index 000000000000..f001896cc497 --- /dev/null +++ b/errorprone/tests/res/android/os/Handler.java @@ -0,0 +1,23 @@ +/* + * 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.os; + +public class Handler { + public void handleMessage(Message msg) { + throw new UnsupportedOperationException(); + } +} diff --git a/errorprone/tests/res/android/os/IBinder.java b/errorprone/tests/res/android/os/IBinder.java new file mode 100644 index 000000000000..214a396d4fde --- /dev/null +++ b/errorprone/tests/res/android/os/IBinder.java @@ -0,0 +1,23 @@ +/* + * 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.os; + +public interface IBinder { + public interface DeathRecipient { + public void binderDied(); + } +} diff --git a/errorprone/tests/res/android/os/Message.java b/errorprone/tests/res/android/os/Message.java new file mode 100644 index 000000000000..2421263969e9 --- /dev/null +++ b/errorprone/tests/res/android/os/Message.java @@ -0,0 +1,20 @@ +/* + * 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.os; + +public class Message { +} 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 d4f229cb3e09..3af0ff0dfb36 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 @@ public class PipMediaController { } 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/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 85d1bc533401..4dc1cca9848d 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6887,7 +6887,6 @@ public class AudioManager { * * @return true if successful, otherwise false */ - @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) public @EncodedSurroundOutputMode int getEncodedSurroundMode() { try { return getService().getEncodedSurroundMode( @@ -6944,7 +6943,6 @@ public class AudioManager { * * @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/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java index 9eacc74843f9..e7d30ebba4b1 100644 --- a/media/java/android/media/session/PlaybackState.java +++ b/media/java/android/media/session/PlaybackState.java @@ -19,6 +19,7 @@ import android.annotation.DrawableRes; 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 @@ public final class PlaybackState implements Parcelable { /** * 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 4719772075ce..9e2cd3e8a7fd 100644 --- a/packages/Connectivity/framework/api/module-lib-current.txt +++ b/packages/Connectivity/framework/api/module-lib-current.txt @@ -13,7 +13,7 @@ package android.net { 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 63e0fe945abb..2194575f20ba 100644 --- a/packages/Connectivity/framework/api/system-current.txt +++ b/packages/Connectivity/framework/api/system-current.txt @@ -239,6 +239,7 @@ package android.net { 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 c4a0d69d3a8a..96f2de6f76bd 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -5362,10 +5362,10 @@ public class ConnectivityManager { * {@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 @@ public class ConnectivityManager { 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 26cb1ed6b4b4..9a58add5d2ba 100644 --- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl +++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl @@ -42,4 +42,5 @@ oneway interface INetworkAgentRegistry { 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 e41ed72b259c..99f48b49c6b5 100644 --- a/packages/Connectivity/framework/src/android/net/LinkProperties.java +++ b/packages/Connectivity/framework/src/android/net/LinkProperties.java @@ -686,8 +686,8 @@ public final class LinkProperties implements Parcelable { } /** - * 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 3622c1c669db..518d3f39d113 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.NonNull; 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 @@ public abstract class NetworkAgent { 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 abstract class NetworkAgent { */ 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 @@ public abstract class NetworkAgent { 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 cfb7325c1b19..0665af58498c 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkProvider.java +++ b/packages/Connectivity/framework/src/android/net/NetworkProvider.java @@ -167,7 +167,15 @@ public class NetworkProvider { 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 b0752e99d892..7be7deb999e2 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkScore.java +++ b/packages/Connectivity/framework/src/android/net/NetworkScore.java @@ -48,7 +48,14 @@ public final class NetworkScore implements Parcelable { }) 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 dcbdc07d1335..cec8b3294418 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 13c7523317fe..9c096d28c5c8 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 14ccd807efe8..70b826a5fc2e 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 1f7672f41abf..f685d888ffa3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -541,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 da78a7c24d0f..3363f8ea808c 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 @@ class ActivityLaunchAnimator(context: Context) { } 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 @@ class ActivityLaunchAnimator(context: Context) { return } - val navigationBar = remoteAnimationTargets.firstOrNull { + val navigationBar = remoteAnimationNonAppTargets.firstOrNull { it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR } diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml index 00c27bf5f10d..1cef44b3b3df 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 f35f5d1f9d76..6725a26c102c 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 dc9d92001351..bb82f91fe2a0 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 f2038397014f..d42cc0212fd8 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 000000000000..ec29d18d607d --- /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 b85d6b097ec7..2402bd710eca 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"/> - - <LinearLayout - android:orientation="vertical" - android:paddingStart="8dp" - android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_width="48dp" + android:layout_height="48dp" /> - <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 7cce1ba36bd9..6a1be81dadf5 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 02179722b35c..343b398e3003 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" > - - <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" > - <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@android:id/edit" - 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_edit" - android:focusable="true" - android:padding="@dimen/qs_footer_icon_padding" - android:src="@*android:drawable/ic_mode_edit" - android:tint="?android:attr/colorForeground"/> + 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="wrap_content" + 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:gravity="center_vertical" + android:ellipsize="marquee" android:focusable="true" + android:gravity="center_vertical" 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: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="@integer/qs_footer_actions_width" - android:layout_height="match_parent" - android:layout_weight="@integer/qs_footer_actions_weight" - android:gravity="center_vertical|end" > + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical"> <com.android.systemui.statusbar.AlphaOptimizedImageView - android:id="@+id/pm_lite" - android:layout_width="@dimen/qs_footer_action_button_size" + android:id="@android:id/edit" + 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_power_menu" + android:contentDescription="@string/accessibility_quick_settings_edit" 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: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="@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 343b398e3003..000000000000 --- 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 5c8b2b08324f..c83077371bb0 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_tile_layout.xml b/packages/SystemUI/res/layout/qs_paged_tile_layout.xml index 46a7cf6440bb..c3f11138129f 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_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml index dc595eecf890..3d2a621756f0 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 5c77d16f2b36..91220e504a7e 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-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 46ec23c8cf2e..ea456d81aa25 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 51d7b8eff5fc..007f81b45bf7 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 e6c5bd0265ed..8f88950b0524 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 a6321fe14894..e2b2e2590b23 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 302e5e417d82..000000000000 --- 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 a33f1312521f..000000000000 --- 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 108d6cf16fec..97b6da1ac6a7 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 e4bdbf3f0727..f489fe846ff3 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 f699198f93d9..bf13c2178d45 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 2355650907df..5feb95783245 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 ad0088256945..62ac75e33ca5 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 bbf204844e29..5827f4e6ad3a 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 4ae1c936d1a9..e55142bcde28 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 e3861470891d..68e6ca8f3f21 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/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt index 8cd68ef8acbc..ab15630a6975 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt @@ -18,7 +18,7 @@ data class KeyguardFaceListenModel( 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 67ee1f45048e..138dd15b33b7 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -353,18 +353,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } }; - 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:AOD_INTERRUPT_END"); } - mAuthController.onCancelAodInterrupt(); + mAuthController.onCancelUdfps(); mIsUdfpsRunningWhileDozing = false; } }; @@ -1631,6 +1629,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mIsFaceEnrolled = whitelistIpcs( () -> mFaceManager != null && mFaceManager.isHardwareDetected() && mFaceManager.hasEnrolledTemplates(userId) - && mFaceSettingEnabledForUser.get(userId)); + && mBiometricEnabledForUser.get(userId)); } /** @@ -2099,7 +2103,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab shouldListenForFingerprintAssistant() || (mKeyguardOccluded && mIsDreaming)) && !mSwitchingUser && !isFingerprintDisabled(getCurrentUser()) && (!mKeyguardGoingAway || !mDeviceInteractive) && mIsPrimaryUser - && allowedOnBouncer; + && allowedOnBouncer && mBiometricEnabledForUser.get(getCurrentUser()); return shouldListen; } @@ -2158,7 +2162,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab (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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab isFaceDisabled(user), becauseCannotSkipBouncer, mKeyguardGoingAway, - mFaceSettingEnabledForUser.get(user), + mBiometricEnabledForUser.get(user), mLockIconPressed, strongAuthAllowsScanning, mIsPrimaryUser, @@ -3235,6 +3239,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab 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/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index ed8f32f31035..0c7b55d6ecbb 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 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } /** - * 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 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, 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 aa818bfdadeb..9239a8ade615 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.NonNull; 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.FingerprintManager; 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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { 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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { }; @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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // 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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // Do nothing. } return handled; - }; + } @Inject public UdfpsController(@NonNull Context context, @@ -324,6 +379,9 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // 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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mCancelAodTimeoutAction = null; } mIsAodInterruptActive = false; - onFingerUp(); } // This method can be called from the UI thread. @@ -598,6 +661,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // 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 @@ public class UdfpsController implements DozeReceiver, HbmCallback { // 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/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 6d1109ee5f51..3544f60601a8 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 @@ public class NavigationBarController implements Callbacks, 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 @@ public class NavigationBarController implements Callbacks, 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 @@ public class NavigationBarController implements Callbacks, 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 @@ public class NavigationBarController implements Callbacks, @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 @@ public class NavigationBarController implements Callbacks, @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 @@ public class NavigationBarController implements Callbacks, return; } + if (isThreeButtonTaskbarEnabled()) { + return; + } + final int displayId = display.getDisplayId(); final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY; final IWindowManager wms = WindowManagerGlobal.getWindowManagerService(); @@ -398,6 +442,24 @@ public class NavigationBarController implements Callbacks, 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/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 32723b4db6a3..f7fa5bfc4248 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.animation.PropertyValuesHolder; 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { 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 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout { // 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 class PagedTileLayout extends ViewPager implements QSTileLayout { } }; - 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 ea471b957d68..cefcd4a5194c 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.TouchAnimator.Listener; 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha mHost = qsTileHost; mExecutor = executor; mTunerService = tunerService; - mFeatureFlags = featureFlags; mQSExpansionPathInterpolator = qsExpansionPathInterpolator; mHost.addCallback(this); mQsPanelController.addOnAttachStateChangeListener(this); @@ -247,7 +244,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha + mQs.getHeader().getPaddingBottom(); firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0); - boolean qsSideLabelsEnabled = mFeatureFlags.isQSLabelsEnabled(); int qqsTileHeight = 0; if (mQsPanelController.areThereTiles()) { @@ -275,22 +271,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha 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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha .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 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha // 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 586176f1c385..bf9acc27ebf6 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.Context; 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 @@ public class QSContainerImpl extends FrameLayout { private QuickStatusBarHeader mHeader; private float mQsExpansion; private QSCustomizer mQSCustomizer; - private View mDragHandle; private NonInterceptingScrollView mQSPanelContainer; private View mBackground; @@ -84,7 +82,6 @@ public class QSContainerImpl extends FrameLayout { 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 @@ public class QSContainerImpl extends FrameLayout { 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 class QSContainerImpl extends FrameLayout { public void setExpansion(float expansion) { mQsExpansion = expansion; - mDragHandle.setAlpha(1.0f - expansion); updateExpansion(); } @@ -296,9 +290,6 @@ public class QSContainerImpl extends FrameLayout { 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 eb7b115700a7..37187135968e 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 @@ public class QSFooterView extends FrameLayout { private float mExpansionAmount; protected View mEdit; - protected View mEditContainer; private TouchAnimator mSettingsCogAnimator; private View mActionsContainer; @@ -107,7 +106,6 @@ public class QSFooterView extends FrameLayout { 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 @@ public class QSFooterView extends FrameLayout { .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 @@ public class QSFooterView extends FrameLayout { 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 27cc2687606b..f89e70a08cc7 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.Bundle; 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 @@ public class QSPanel extends LinearLayout implements Tunable { private PageIndicator mFooterPageIndicator; private int mContentMarginStart; private int mContentMarginEnd; - private int mVisualTilePadding; private boolean mUsingHorizontalLayout; private Record mDetailRecord; @@ -111,9 +106,7 @@ public class QSPanel extends LinearLayout implements Tunable { 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 @@ public class QSPanel extends LinearLayout implements Tunable { } - 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 class QSPanel extends LinearLayout implements Tunable { 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 class QSPanel extends LinearLayout implements Tunable { } 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 @@ public class QSPanel extends LinearLayout implements Tunable { @Override protected void onFinishInflate() { super.onFinishInflate(); + mFooter = findViewById(R.id.qs_footer); mDivider = findViewById(R.id.divider); } @@ -638,60 +612,10 @@ public class QSPanel extends LinearLayout implements Tunable { // 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 @@ public class QSPanel extends LinearLayout implements Tunable { 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 eda1abb0997e..5b6b5dfd123d 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 static com.android.systemui.qs.dagger.QSFragmentModule.QS_USING_MEDIA_PLA 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 class QSPanelController extends QSPanelControllerBase<QSPanel> { } /** */ - 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 e41a0389e8c1..925c9ebfe298 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 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr 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 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr 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 e7828c366b64..63733b392631 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 @@ public class QuickQSPanel extends QSPanel { } @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 @@ public class QuickQSPanel extends QSPanel { @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 30a08c6f1b66..7518b200c7e2 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 @@ public class QSCustomizer extends LinearLayout { } } - /** - * 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 006b23098622..50805330cf1f 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.ArrayList; import java.util.List; import javax.inject.Inject; -import javax.inject.Named; /** */ @QSScope @@ -110,25 +107,21 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta 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 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } 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 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta 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 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta 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 10192bc20df9..a1e1d64abdc7 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 @@ import dagger.Provides; @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 9b0536c595ad..3437dd595152 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.qs.tiles.WorkModeTile; 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 @@ public class QSFactoryImpl implements QSFactory { 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 @@ public class QSFactoryImpl implements QSFactory { mQsHostLazy = qsHostLazy; mCustomTileBuilderProvider = customTileBuilderProvider; - mSideLabels = useSideLabels; - mWifiTileProvider = wifiTileProvider; mInternetTileProvider = internetTileProvider; mBluetoothTileProvider = bluetoothTileProvider; @@ -251,12 +243,6 @@ public class QSFactoryImpl implements QSFactory { @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 aa8ce85f5950..ba69dd530eac 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.QSIconView; 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 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy 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 ec3a857dbc84..17b489ca2490 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 @@ public class FeatureFlags { 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/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index c811fdd2fe2a..a537299d4979 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.Color; 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 @@ public class ScrimView extends View { private Executor mChangeRunnableExecutor; private Executor mExecutor; private Looper mExecutorLooper; + @Nullable + private Rect mDrawableBounds; public ScrimView(Context context) { this(context, null); @@ -125,7 +129,9 @@ public class ScrimView extends View { @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 @@ public class ScrimView extends View { ((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/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 707135c3d95b..30d9841e41ee 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 @@ open class KeyguardBypassController : Dumpable { 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 @@ open class KeyguardBypassController : Dumpable { * 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 @@ open class KeyguardBypassController : Dumpable { 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 @@ open class KeyguardBypassController : Dumpable { 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 6a35293def76..1c8bda7f7344 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 @@ public class NotificationPanelViewController extends PanelViewController { 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 = getExpandedHeight() - mSplitShadeNotificationsTopPadding; + 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 @@ public class NotificationPanelViewController extends PanelViewController { 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 58488ef8ffd2..5e9c758da07a 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } /** + * 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (isNaN(expansionFraction)) { return; } - shiftNotificationsScrim(qsPanelBottomY); updateNotificationsScrimAlpha(expansionFraction, qsPanelBottomY); if (mQsExpansion != expansionFraction) { mQsExpansion = expansionFraction; @@ -511,14 +517,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - 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/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index d544f7378f8a..42cc1fa99909 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.PackageManager; 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } 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 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { authCallback.onAuthenticationFailed(); // THEN aod interrupt is cancelled - verify(mAuthController).onCancelAodInterrupt(); + verify(mAuthController).onCancelUdfps(); } @Test @@ -557,7 +560,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { 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/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index bbd3ce89b997..0aa182fb1e81 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 @@ public class UdfpsControllerTest extends SysuiTestCase { 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/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index acedf59bdd14..4f8859927d06 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 @@ public class QSPanelTest extends SysuiTestCase { 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 62cc9b7e3602..3d53062d7d02 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 @@ public class TileAdapterTest extends SysuiTestCase { 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 87a77577841a..c2e58efe1328 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 @@ package com.android.systemui.statusbar; 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 @@ public class ScrimViewTest extends LeakCheckedTest { 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/BluetoothAirplaneModeListener.java b/services/core/java/com/android/server/BluetoothAirplaneModeListener.java index aa56da5773e9..197321f1cb6a 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 @@ class BluetoothAirplaneModeListener { } @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 09cfac005677..feed2205dc41 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.content.pm.PackageManager.PERMISSION_GRANTED; 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void onAirplaneModeChanged() { synchronized (this) { if (isBluetoothPersistedStateOn()) { @@ -707,9 +723,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (!checkConnectPermissionForPreflight(mContext)) { - return; - } if (callback == null) { Slog.w(TAG, "registerStateChangeCallback: Callback is null!"); return; @@ -720,9 +733,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { - if (!checkConnectPermissionForPreflight(mContext)) { - return; - } if (callback == null) { Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!"); return; @@ -935,6 +945,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return appCount; } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private boolean checkBluetoothPermissions(String packageName, boolean requireForeground) { if (isBluetoothDisallowed()) { if (DBG) { @@ -990,6 +1001,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * * @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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } + @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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private void handleEnable(boolean quietMode) { mQuietEnable = quietMode; @@ -2418,6 +2442,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return true; } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private void handleDisable() { try { mBluetoothLock.readLock().lock(); @@ -2475,6 +2500,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { 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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } + @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 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * * <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 242fa848c25e..3642e4dccf34 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 @@ public class BluetoothModeChangeHelper { } @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 d9cc4b41f797..4d96162149e2 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1353,8 +1353,8 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index df6ab5da70d4..3ba4c34fb1a7 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -19,9 +19,10 @@ package com.android.server; 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 @@ public final class SensorPrivacyService extends SystemService { 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 @@ public final class SensorPrivacyService extends SystemService { } } + 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 final class SensorPrivacyService extends SystemService { 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 @@ public final class SensorPrivacyService extends SystemService { mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled); SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser = mIndividualSensorListeners.get(userId); + + setUserRestriction(userId, sensor, enabled); + if (listenersForUser == null) { return; } @@ -1152,6 +1170,18 @@ public final class SensorPrivacyService extends SystemService { } } + 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 611fe7ad8c78..98bfa28d6415 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -376,7 +376,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { private final LocalLog mListenLog = new LocalLog(200); - private List<PhysicalChannelConfig> mPhysicalChannelConfigs; + private List<List<PhysicalChannelConfig>> mPhysicalChannelConfigs; private boolean[] mIsDataEnabled; @@ -716,7 +716,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { try { r.callback.onPhysicalChannelConfigChanged( shouldSanitizeLocationForPhysicalChannelConfig(r) - ? getLocationSanitizedConfigs(mPhysicalChannelConfigs) - : mPhysicalChannelConfigs); + ? getLocationSanitizedConfigs( + mPhysicalChannelConfigs.get(phoneId)) + : mPhysicalChannelConfigs.get(phoneId)); } catch (RemoteException ex) { remove(r.binder); } @@ -2568,7 +2569,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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 2a1a8971ec8c..e3b06d696423 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 @@ public class ActivityManagerService extends IActivityManager.Stub @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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub | 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 class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub : 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub | 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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 42aac295c027..6fa8ecd41d7c 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 @@ final class ActivityManagerShellCommand extends ShellCommand { 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 a5474d0b8c72..f0b116cc1869 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 @@ public final class BroadcastQueue { skip = true; } } + boolean isSingleton = false; try { isSingleton = mService.isSingleton(info.activityInfo.processName, @@ -1553,6 +1554,37 @@ public final class BroadcastQueue { + 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 198ba34e3956..801559620457 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 class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { callerInstantApp = _callerInstantApp; resolvedType = _resolvedType; requiredPermissions = _requiredPermissions; + excludedPermissions = _excludedPermissions; appOp = _appOp; options = _options; receivers = _receivers; @@ -299,6 +305,7 @@ final class BroadcastRecord extends Binder { 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 @@ final class BroadcastRecord extends Binder { // 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 984fe409b086..756209824614 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 @@ public abstract class PreBootBroadcaster extends IIntentReceiver.Stub { 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 a5d0e72a81ae..ba3e1fb95e7d 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 @@ class UserController implements Handler.Callback { // 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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 419e686e237d..3f075724662f 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 @@ public class AppOpsService extends IAppOpsService.Stub { } } - 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 282a12da6bb8..96bb73f3107c 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 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - 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 @@ import java.util.concurrent.atomic.AtomicBoolean; 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 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*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 @@ import java.util.concurrent.atomic.AtomicBoolean; // 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 9707aced2de5..edacd40049b4 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub * 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 c57d5afa5f0c..52e8edff5ffa 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 @@ public class BtHelper { } 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 cb7c568757e5..a546a60e20ef 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.database.ContentObserver; 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.FingerprintManager; 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { @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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { } } - 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 @@ public class BiometricService extends SystemService { 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 @@ public class BiometricService extends SystemService { } 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 262cb36d7bdb..c4bd18b59745 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 @@ class PreAuthInfo { 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/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 1bbcedeb0494..b2d35f45ab44 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.ActivityManager; 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.ICameraServiceProxy; 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.ArraySet; 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 @@ public class CameraServiceProxy extends SystemService } } + 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService } @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 @@ public class CameraServiceProxy extends SystemService /** * 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 @@ public class CameraServiceProxy extends SystemService // 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 @@ public class CameraServiceProxy extends SystemService 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 4d310cbc06bb..584174e237a3 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.internal.util.WakeupMessage; 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 class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa */ 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa @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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa mHandler = handler; this.factorySerialNumber = factorySerialNumber; this.creatorUid = creatorUid; + mLingerDurationMs = lingerDurationMs; mQosCallbackTracker = qosCallbackTracker; } @@ -685,6 +691,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa /** * 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa } /** + * 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa } 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 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo>, NetworkRa } /** - * 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/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index abcf4fb939e1..b10d56b62acc 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 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR if (mPlaybackState == null) { return false; } - return mPlaybackState.isActive() == expected; + return mPlaybackState.isActiveState() == expected; } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 3ddfe6cdbdb4..1e6e5cb97f45 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } 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 @@ public class NotificationManagerService extends SystemService { } } + 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { /** 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 @@ public class NotificationManagerService extends SystemService { * 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 a9dbb2f29535..94a1f3b1c502 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 @@ package com.android.server.om; 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.internal.util.CollectionUtils; 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 @@ public class OverlayReferenceMapper { * 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 @@ public class OverlayReferenceMapper { * * @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 @@ public class OverlayReferenceMapper { * 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 @@ public class OverlayReferenceMapper { * * 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 @@ public class OverlayReferenceMapper { } 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 @@ public class OverlayReferenceMapper { * * 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 @@ public class OverlayReferenceMapper { if (TextUtils.isEmpty(actor)) { continue; } - addOverlayToMap(actor, targetPkgName, overlay); + addOverlayToMap(actor, targetPkgName, overlay, changedPackages); } } } @@ -312,7 +359,8 @@ public class OverlayReferenceMapper { 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 @@ public class OverlayReferenceMapper { } } - 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 ca8202f5f94b..4f527f26cbb3 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 @@ public class AppsFilter implements Watchable, Snappable { 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 @@ public class AppsFilter implements Watchable, Snappable { } } - 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 @@ public class AppsFilter implements Watchable, Snappable { final AndroidPackage newPkg = newPkgSetting.pkg; if (newPkg == null) { - // nothing to add - return; + return null; } if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) { @@ -765,8 +785,13 @@ public class AppsFilter implements Watchable, Snappable { 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 @@ public class AppsFilter implements Watchable, Snappable { } } - 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 @@ public class AppsFilter implements Watchable, Snappable { 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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 91a66acf433d..dd80e167f0b3 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 @@ public class LauncherAppsService extends SystemService { 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 284998203ac0..85c5a5ea84b8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -15315,9 +15315,8 @@ public class PackageManagerService extends IPackageManager.Stub 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 +15326,8 @@ public class PackageManagerService extends IPackageManager.Stub 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 +22195,7 @@ public class PackageManagerService extends IPackageManager.Stub 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) { } @@ -27810,8 +27808,8 @@ public class PackageManagerService extends IPackageManager.Stub }; 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/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index e913829ea9e2..81cfbf7437fa 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 bf2b3c7f491f..a8a6a723ce74 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.IntentFilter; 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.compat.PlatformCompat; 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 @@ public class DomainVerificationCollector { * 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 2a17c6d4cec5..3f00a9d999aa 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.content.pm.verify.domain.IDomainVerificationManager; 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 class DomainVerificationManagerStub extends IDomainVerificationManager.St 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 4ae79a209524..f0fdad016d55 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 class DomainVerificationService extends SystemService } 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 44ff3eb4b942..246810f4d796 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.Intent; 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.PackageManagerService; 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 @@ public final class DomainVerificationUtils { 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 474ce59212ff..816c81ddf305 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 @@ public class FaceDownDetector implements SensorEventListener { 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 4adcfb62ef50..9e19f57ed0e2 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 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba 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/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 20c1c3c8b738..14cab382d405 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -120,6 +120,7 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @Override public boolean updateConfiguration(TimeConfiguration timeConfiguration) { + enforceManageTimeDetectorPermission(); // TODO(b/172891783) Add actual logic return false; } diff --git a/services/core/java/com/android/server/trust/OWNERS b/services/core/java/com/android/server/trust/OWNERS index b039c4b45447..e2c6ce15b51e 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 a59b368ec321..48ccad33e631 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 @@ public class UnderlyingNetworkTracker { 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/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d16ca4a53a04..a3624e014e30 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -5898,6 +5898,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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(); } } @@ -6714,26 +6720,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 75a188ed86a2..1158a9c70158 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 @@ class ActivityStarter { 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 bdde3692ef53..2b1cf395dcd7 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 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } final void scheduleIdle() { - mHandler.sendEmptyMessage(IDLE_NOW_MSG); + if (!mHandler.hasMessages(IDLE_NOW_MSG)) { + mHandler.sendEmptyMessage(IDLE_NOW_MSG); + } } /** @@ -2115,8 +2117,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks { } } - 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 128d452c3018..d920267bb44d 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 @@ package com.android.server.wm; 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 @@ final class BlurController { } + 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/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 5ffab483202f..a467d82b362e 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 @@ class TaskSnapshotController { 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 7650fa1f3f5e..c80a38fdb838 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 @@ public class WindowManagerService extends IWindowManager.Stub final TaskSnapshotController mTaskSnapshotController; - final BlurController mBlurController = new BlurController(); + final BlurController mBlurController; boolean mIsTouchDevice; boolean mIsFakeTouchDevice; @@ -1418,6 +1418,8 @@ public class WindowManagerService extends IWindowManager.Stub setGlobalShadowSettings(); mAnrController = new AnrController(this); mStartingSurfaceController = new StartingSurfaceController(this); + + mBlurController = new BlurController(mContext, mPowerManager); } DisplayAreaPolicy.Provider getDisplayAreaPolicyProvider() { @@ -1740,8 +1742,11 @@ public class WindowManagerService extends IWindowManager.Stub } // 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) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ddcb2bf7be22..cf9b88a33527 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { + " 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 b136d00fccac..83677c2a2242 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -36,6 +36,10 @@ java_test_host { ], 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 000000000000..558d01ed203d --- /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 000000000000..92dcd348a8e2 --- /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/layout/qs_paged_page_side_labels.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml index c83077371bb0..21e4432b299f 100644 --- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 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,10 +13,10 @@ 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" /> + +<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 index b8ea622fb385..f0b85864ab6b 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 000000000000..57184748d074 --- /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 000000000000..a92a14f68200 --- /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 000000000000..7537247f4e8f --- /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 000000000000..2bb6b82a6bbc --- /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/layout/qs_paged_tile_layout_side_labels.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml index efa240362f67..6038cb1ec20c 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout_side_labels.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 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. @@ -15,13 +14,6 @@ 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" /> +<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 000000000000..4625e3bcd041 --- /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/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.xml new file mode 100644 index 000000000000..283f5c13256e --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/public.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> + <public-group type="string" first-id="0x7f010000"> + <public name="policy_public" /> + </public-group> +</resources> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml new file mode 100644 index 000000000000..822194fa7ebb --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res/values/values.xml @@ -0,0 +1,19 @@ +<?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> + <string name="policy_public">Not overlaid</string> +</resources> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.xml new file mode 100644 index 000000000000..0100389f690a --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/res_overlayable/values/overlayable.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. +--> + +<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 dce853afc763..4de8d52a8c2e 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 @@ class DomainVerificationCollectorTest { <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 @@ class DomainVerificationCollectorTest { 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 14c02d53efad..0a5a3bff3676 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.NonNull; 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 @@ class DomainVerificationJavaUtil { 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 7fea4ee42317..3838f68fcf22 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 @@ class DomainVerificationManagerApiTest { 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 000000000000..98634b28ea9d --- /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/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index edfc21d47767..7654093f9e7e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -2223,7 +2223,11 @@ public class AlarmManagerServiceTest { } @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 +2243,26 @@ public class AlarmManagerServiceTest { } @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 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/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java index 5bef877e2f39..e9b5b6243089 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 @@ public class BroadcastRecordTest { 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/appsearch/AppSearchImplPlatformTest.java b/services/tests/servicestests/src/com/android/server/appsearch/AppSearchImplPlatformTest.java index 4240581bc504..b552fd5e8718 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 android.content.pm.PackageManager; 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 @@ public class AppSearchImplPlatformTest { 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 @@ public class AppSearchImplPlatformTest { 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 @@ public class AppSearchImplPlatformTest { @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 @@ public class AppSearchImplPlatformTest { @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 @@ public class AppSearchImplPlatformTest { @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 @@ public class AppSearchImplPlatformTest { 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 8d35ebe8e2e1..11ae76b94495 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 android.content.pm.PackageManager; 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 @@ public class VisibilityStoreTest { @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 @@ public class VisibilityStoreTest { @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 ba4d5856b247..380d9be5ae57 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 android.util.ArraySet; 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.StringIndexingConfig; 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.Rule; 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 @@ public class AppSearchImplTest { // 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 @@ public class AppSearchImplTest { "package$newDatabase/RefType") .build()) .build()) + .addTypes( + SchemaTypeConfigProto.newBuilder() + .setSchemaType("package$newDatabase/RefType") + .build()) .build(); existingTypes.addAll(expectedSchema.getTypesList()); @@ -216,12 +243,12 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { .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 @@ public class AppSearchImplTest { .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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { // 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { assertThat( mAppSearchImpl.rewriteSearchSpecForPrefixesLocked( searchSpecProto, - Collections.singleton( - AppSearchImpl.createPrefix("package", "database")), + Collections.singleton(createPrefix("package", "database")), /*allowedPrefixedSchemas=*/ Collections.emptySet())) .isFalse(); } @@ -1082,7 +1104,7 @@ public class AppSearchImplTest { // 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 @@ public class AppSearchImplTest { 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 class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 @@ public class AppSearchImplTest { 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 70e1e05174ef..63f031722ede 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class GenericDocumentToProtoConverterTest { DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder() .setUri("uri1") - .setSchema("schemaType1") + .setSchema(SCHEMA_TYPE_1) .setCreationTimestampMs(5L) .setScore(1) .setTtlMs(1L) @@ -109,9 +124,133 @@ public class GenericDocumentToProtoConverterTest { 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 d07211fe2028..26fac492ccd2 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 static com.google.common.truth.Truth.assertThat; 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 com.android.server.appsearch.proto.SnippetProto; 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 @@ public class SnippetTest { DocumentProto documentProto = DocumentProto.newBuilder() .setUri(uri) - .setSchema(schemaType) + .setSchema(SCHEMA_TYPE) .addProperties(property) .build(); SnippetProto snippetProto = @@ -86,8 +97,9 @@ public class SnippetTest { 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 @@ public class SnippetTest { + " 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 @@ public class SnippetTest { DocumentProto documentProto = DocumentProto.newBuilder() .setUri(uri) - .setSchema(schemaType) + .setSchema(SCHEMA_TYPE) .addProperties(property) .build(); SearchResultProto.ResultProto resultProto = @@ -137,8 +148,9 @@ public class SnippetTest { 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 @@ public class SnippetTest { DocumentProto documentProto = DocumentProto.newBuilder() .setUri("uri1") - .setSchema("schema1") + .setSchema(SCHEMA_TYPE) .addProperties(property1) .addProperties(property2) .build(); @@ -203,8 +215,9 @@ public class SnippetTest { 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 79a5ed65b999..5c53d43fa1df 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 @@ public class AudioDeviceBrokerTest { 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 @@ public class AudioDeviceBrokerTest { ((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 e322ce551372..96bab6119154 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 @@ public class AuthSessionTest { componentInfo, type, false /* resetLockoutRequiresHardwareAuthToken */)); + + when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); } private void setupFace(int id, boolean confirmationAlwaysRequired, @@ -329,6 +331,6 @@ public class AuthSessionTest { } }); - 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 a5fbab519aaa..ec3bea33c8db 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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { // 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 @@ public class BiometricServiceTest { @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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { 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 @@ public class BiometricServiceTest { 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 73ec5b8d3522..89798ce12d4c 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_LOW; 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { @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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { .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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 d888b9258d33..876c84547cae 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 @@ class OverlayReferenceMapperTests { @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 @@ class OverlayReferenceMapperTests { 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 @@ class OverlayReferenceMapperTests { ) ) ) - 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 @@ class OverlayReferenceMapperTests { 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 @@ class OverlayReferenceMapperTests { ) ) ) - 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 8070bd1f06a1..558e2591161c 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 9f428c7cbded..67dd0556e098 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 @@ public class AppsFilterTest { 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 @@ public class AppsFilterTest { 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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 87efaa270ec4..37d7198ef150 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index dd0c9e6d390e..d9aa871447be 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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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.content.pm.PackageManager; 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 @@ public class WindowManagerServiceTests extends WindowTestsBase { 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/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 2e692e6e68f7..7f24c365237d 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser 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 bcfb302356c1..6f701f7e3a36 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 @@ final class HotwordDetectionConnection { } 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/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index c92d40cdd555..db91eb21d532 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 @@ class CloseAppBackButtonTest(testSpec: FlickerTestParameter) : CloseAppTransitio } } + @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 0bae8f617038..3bd19ea74f1d 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 @@ import org.junit.runners.Parameterized @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 1c97be32629f..ad7ee3030ea8 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 @@ class OpenAppFromOverviewTest(testSpec: FlickerTestParameter) : OpenAppTransitio 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 742a2d1b0eeb..a353c5962582 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 @@ class SeamlessAppRotationTest( 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 @@ class SeamlessAppRotationTest( } } - @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 0bb0337b3b09..642b19e6d961 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 @@ public class StagedRollbackTest { } @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 304567a34ed3..1aa5c249ff18 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.Test; 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt index 454d5b5266ee..2b45b3d69ce9 100644 --- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt +++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt @@ -44,6 +44,9 @@ class NetworkAgentConfigTest { 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 @@ class NetworkAgentConfigTest { if (isAtLeastS()) { setNat64DetectionEnabled(false) setProvisioningNotificationEnabled(false) + setBypassableVpn(true) } }.build() @@ -78,6 +82,7 @@ class NetworkAgentConfigTest { 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 730ef3b98fa7..39c424e31f0e 100644 --- a/tests/net/integration/Android.bp +++ b/tests/net/integration/Android.bp @@ -62,6 +62,7 @@ android_test { // 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 6cbdd258c00a..19f884346e6f 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -384,7 +384,7 @@ public class ConnectivityManagerTest { 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 47e4b5e4942a..039ce2f2695d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -4313,7 +4313,7 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 116d755e30a4..36e229d8aa73 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 @@ public class LingerMonitorTest { 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 @@ public class LingerMonitorTest { 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 b592000e38f9..0c7363e55cc6 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 @@ public class UnderlyingNetworkTrackerTest { 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 @@ public class UnderlyingNetworkTrackerTest { verify(mConnectivityManager) .requestBackgroundNetwork( eq(getRouteSelectionRequest(INITIAL_SUB_IDS)), - any(), - mRouteSelectionCallbackCaptor.capture()); + mRouteSelectionCallbackCaptor.capture(), + any()); RouteSelectionCallback cb = mRouteSelectionCallbackCaptor.getValue(); cb.onAvailable(mNetwork); |