diff options
460 files changed, 12614 insertions, 4523 deletions
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java index 35cea3e2a67b..6c624264b9be 100644 --- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java +++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchBatchResult.java @@ -26,6 +26,7 @@ import com.android.internal.util.Preconditions; import java.util.Collections; import java.util.Map; +import java.util.Objects; /** * Provides results for AppSearch batch operations which encompass multiple documents. @@ -180,7 +181,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl public Builder<KeyType, ValueType> setSuccess( @NonNull KeyType key, @Nullable ValueType result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return setResult(key, AppSearchResult.newSuccessfulResult(result)); } @@ -198,7 +199,7 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl @AppSearchResult.ResultCode int resultCode, @Nullable String errorMessage) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); + Objects.requireNonNull(key); return setResult(key, AppSearchResult.newFailedResult(resultCode, errorMessage)); } @@ -214,8 +215,8 @@ public final class AppSearchBatchResult<KeyType, ValueType> implements Parcelabl public Builder<KeyType, ValueType> setResult( @NonNull KeyType key, @NonNull AppSearchResult<ValueType> result) { Preconditions.checkState(!mBuilt, "Builder has already been used"); - Preconditions.checkNotNull(key); - Preconditions.checkNotNull(result); + Objects.requireNonNull(key); + Objects.requireNonNull(result); if (result.isSuccess()) { mSuccesses.put(key, result.getResultValue()); mFailures.remove(key); diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchResult.java index 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..f6f5c98856c7 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,9 +51,9 @@ 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; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.appsearch.external.localstorage.AppSearchImpl; @@ -64,6 +61,8 @@ import com.android.server.appsearch.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; @@ -75,7 +74,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; -import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -94,7 +93,7 @@ public class AppSearchManagerService extends SystemService { // mutate requests will need to gain write lock and query requests need to gain read lock. private static final Executor EXECUTOR = new ThreadPoolExecutor(/*corePoolSize=*/1, Runtime.getRuntime().availableProcessors(), /*keepAliveTime*/ 60L, TimeUnit.SECONDS, - new SynchronousQueue<Runnable>()); + new LinkedBlockingQueue<>()); // Cache of unlocked user ids so we don't have to query UserManager service each time. The // "locked" suffix refers to the fact that access to the field should be locked; unrelated to @@ -121,14 +120,6 @@ 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 +127,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 +155,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); } } @@ -224,10 +180,10 @@ public class AppSearchManagerService extends SystemService { int schemaVersion, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(schemaBundles); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(schemaBundles); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -273,9 +229,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -300,9 +256,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -328,10 +284,10 @@ public class AppSearchManagerService extends SystemService { @UserIdInt int userId, @ElapsedRealtimeLong long binderCallStartTimeMillis, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(documentBundles); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(documentBundles); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -364,6 +320,8 @@ 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); @@ -400,11 +358,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Map<String, List<String>> typePropertyPaths, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(namespace); - Preconditions.checkNotNull(uris); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(namespace); + Objects.requireNonNull(uris); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -445,11 +403,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -480,10 +438,10 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -512,7 +470,7 @@ public class AppSearchManagerService extends SystemService { long nextPageToken, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(callback); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); // TODO(b/162450968) check nextPageToken is being advanced by the same uid as originally @@ -631,7 +589,7 @@ public class AppSearchManagerService extends SystemService { } } } - impl.persistToDisk(); + impl.persistToDisk(PersistType.Code.FULL); invokeCallbackOnResult(callback, AppSearchResult.newSuccessfulResult(migrationFailureBundles)); } catch (Throwable t) { @@ -685,10 +643,10 @@ public class AppSearchManagerService extends SystemService { @NonNull List<String> uris, @UserIdInt int userId, @NonNull IAppSearchBatchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(uris); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(uris); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -708,6 +666,8 @@ 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); @@ -723,11 +683,11 @@ public class AppSearchManagerService extends SystemService { @NonNull Bundle searchSpecBundle, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(queryExpression); - Preconditions.checkNotNull(searchSpecBundle); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(queryExpression); + Objects.requireNonNull(searchSpecBundle); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -741,6 +701,8 @@ 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); @@ -754,9 +716,9 @@ public class AppSearchManagerService extends SystemService { @NonNull String databaseName, @UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(packageName); - Preconditions.checkNotNull(databaseName); - Preconditions.checkNotNull(callback); + Objects.requireNonNull(packageName); + Objects.requireNonNull(databaseName); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -785,7 +747,7 @@ 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); } @@ -794,7 +756,7 @@ public class AppSearchManagerService extends SystemService { @Override public void initialize(@UserIdInt int userId, @NonNull IAppSearchResultCallback callback) { - Preconditions.checkNotNull(callback); + Objects.requireNonNull(callback); int callingUid = Binder.getCallingUid(); int callingUserId = handleIncomingUser(userId, callingUid); EXECUTOR.execute(() -> { @@ -825,7 +787,7 @@ public class AppSearchManagerService extends SystemService { } private void verifyCallingPackage(int callingUid, @NonNull String callingPackage) { - Preconditions.checkNotNull(callingPackage); + Objects.requireNonNull(callingPackage); if (mPackageManagerInternal.getPackageUid( callingPackage, /*flags=*/ 0, UserHandle.getUserId(callingUid)) != callingUid) { @@ -873,13 +835,12 @@ 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..4de52fb65d93 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/VisibilityStore.java @@ -35,13 +35,14 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; -import com.android.internal.util.Preconditions; +import com.android.server.appsearch.external.localstorage.util.PrefixUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -153,7 +154,7 @@ 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 = ""; @@ -332,9 +333,9 @@ public class VisibilityStore { @NonNull Set<String> schemasNotPlatformSurfaceable, @NonNull Map<String, List<PackageIdentifier>> schemasPackageAccessible) throws AppSearchException { - Preconditions.checkNotNull(prefix); - Preconditions.checkNotNull(schemasNotPlatformSurfaceable); - Preconditions.checkNotNull(schemasPackageAccessible); + Objects.requireNonNull(prefix); + Objects.requireNonNull(schemasNotPlatformSurfaceable); + Objects.requireNonNull(schemasPackageAccessible); // Persist the document GenericDocument.Builder<?> visibilityDocument = @@ -382,8 +383,8 @@ public class VisibilityStore { /** Checks whether {@code prefixedSchema} can be searched over by the {@code callerUid}. */ public boolean isSchemaSearchableByCaller( @NonNull String prefix, @NonNull String prefixedSchema, int callerUid) { - Preconditions.checkNotNull(prefix); - Preconditions.checkNotNull(prefixedSchema); + Objects.requireNonNull(prefix); + Objects.requireNonNull(prefixedSchema); // We compare appIds here rather than direct uids because the package's uid may change based // on the user that's running. diff --git a/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java index 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/service/java/com/android/server/appsearch/stats/PlatformLogger.java b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java index 1c04d99ac2c7..731ab35bf367 100644 --- a/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java +++ b/apex/appsearch/service/java/com/android/server/appsearch/stats/PlatformLogger.java @@ -29,7 +29,6 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.Preconditions; import com.android.server.appsearch.external.localstorage.AppSearchLogger; import com.android.server.appsearch.external.localstorage.stats.CallStats; import com.android.server.appsearch.external.localstorage.stats.PutDocumentStats; @@ -38,6 +37,7 @@ import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; +import java.util.Objects; import java.util.Random; /** @@ -162,15 +162,15 @@ public final class PlatformLogger implements AppSearchLogger { * Westworld constructor */ public PlatformLogger(@NonNull Context context, int userId, @NonNull Config config) { - mContext = Preconditions.checkNotNull(context); - mConfig = Preconditions.checkNotNull(config); + mContext = Objects.requireNonNull(context); + mConfig = Objects.requireNonNull(config); mUserId = userId; } /** Logs {@link CallStats}. */ @Override public void logStats(@NonNull CallStats stats) { - Preconditions.checkNotNull(stats); + Objects.requireNonNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(stats.getCallType())) { logStatsImplLocked(stats); @@ -181,7 +181,7 @@ public final class PlatformLogger implements AppSearchLogger { /** Logs {@link PutDocumentStats}. */ @Override public void logStats(@NonNull PutDocumentStats stats) { - Preconditions.checkNotNull(stats); + Objects.requireNonNull(stats); synchronized (mLock) { if (shouldLogForTypeLocked(CallStats.CALL_TYPE_PUT_DOCUMENT)) { logStatsImplLocked(stats); @@ -197,7 +197,7 @@ public final class PlatformLogger implements AppSearchLogger { */ public int removeCachedUidForPackage(@NonNull String packageName) { // TODO(b/173532925) This needs to be called when we get PACKAGE_REMOVED intent - Preconditions.checkNotNull(packageName); + Objects.requireNonNull(packageName); synchronized (mLock) { Integer uid = mPackageUidCacheLocked.remove(packageName); return uid != null ? uid : Process.INVALID_UID; diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 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/AppSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java index f0de4962ad3c..61933672a1e1 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/AppSearchSessionShimImpl.java @@ -40,11 +40,11 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -81,8 +81,8 @@ public class AppSearchSessionShimImpl implements AppSearchSessionShim { private AppSearchSessionShimImpl( @NonNull AppSearchSession session, @NonNull ExecutorService executor) { - mAppSearchSession = Preconditions.checkNotNull(session); - mExecutor = Preconditions.checkNotNull(executor); + mAppSearchSession = Objects.requireNonNull(session); + mExecutor = Objects.requireNonNull(executor); } @Override diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java index 5042ce0efbd8..c35849d23c26 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/GlobalSearchSessionShimImpl.java @@ -30,11 +30,11 @@ import android.content.Context; import androidx.test.core.app.ApplicationProvider; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -69,8 +69,8 @@ public class GlobalSearchSessionShimImpl implements GlobalSearchSessionShim { private GlobalSearchSessionShimImpl( @NonNull GlobalSearchSession session, @NonNull ExecutorService executor) { - mGlobalSearchSession = Preconditions.checkNotNull(session); - mExecutor = Preconditions.checkNotNull(executor); + mGlobalSearchSession = Objects.requireNonNull(session); + mExecutor = Objects.requireNonNull(executor); } @NonNull diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java index 5f26e8cba585..72078f855d70 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/SearchResultsShimImpl.java @@ -22,12 +22,12 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchResultsShim; -import com.google.common.base.Preconditions; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -40,8 +40,8 @@ public class SearchResultsShimImpl implements SearchResultsShim { private final SearchResults mSearchResults; SearchResultsShimImpl(@NonNull SearchResults searchResults, @NonNull Executor executor) { - mExecutor = Preconditions.checkNotNull(executor); - mSearchResults = Preconditions.checkNotNull(searchResults); + mExecutor = Objects.requireNonNull(executor); + mSearchResults = Objects.requireNonNull(searchResults); } @NonNull diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchSessionShim.java index 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/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index 113f8fe9e248..6dbbcb564b4c 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -26,8 +26,8 @@ import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; import android.util.Base64; +import android.util.IndentingPrintWriter; -import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java index ca588c509594..09260b775444 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java @@ -37,9 +37,9 @@ import android.permission.PermissionManager; import android.util.ArraySet; import android.util.Base64; import android.util.DebugUtils; +import android.util.IndentingPrintWriter; import android.util.Slog; -import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index 8b12beb57195..e47715685323 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,6 +15,7 @@ */ package com.android.server.blob; +import static android.Manifest.permission.ACCESS_BLOBS_ACROSS_USERS; import static android.app.blob.XmlTags.ATTR_COMMIT_TIME_MS; import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME; @@ -36,6 +37,7 @@ import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_DESC_RES_NAME; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; import static com.android.server.blob.BlobStoreConfig.hasLeaseWaitTimeElapsed; import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId; import static com.android.server.blob.BlobStoreUtils.getPackageResources; @@ -45,15 +47,18 @@ import android.annotation.Nullable; import android.app.blob.BlobHandle; import android.app.blob.LeaseInfo; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.ResourceId; import android.content.res.Resources; import android.os.ParcelFileDescriptor; import android.os.RevocableFileDescriptor; import android.os.UserHandle; +import android.permission.PermissionManager; import android.system.ErrnoException; import android.system.Os; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.util.StatsEvent; @@ -62,7 +67,6 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; @@ -85,7 +89,6 @@ class BlobMetadata { private final long mBlobId; private final BlobHandle mBlobHandle; - private final int mUserId; @GuardedBy("mMetadataLock") private final ArraySet<Committer> mCommitters = new ArraySet<>(); @@ -94,24 +97,23 @@ class BlobMetadata { private final ArraySet<Leasee> mLeasees = new ArraySet<>(); /** - * Contains packageName -> {RevocableFileDescriptors}. + * Contains Accessor -> {RevocableFileDescriptors}. * * Keep track of RevocableFileDescriptors given to clients which are not yet revoked/closed so * that when clients access is revoked or the blob gets deleted, we can be sure that clients * do not have any reference to the blob and the space occupied by the blob can be freed. */ @GuardedBy("mRevocableFds") - private final ArrayMap<String, ArraySet<RevocableFileDescriptor>> mRevocableFds = + private final ArrayMap<Accessor, ArraySet<RevocableFileDescriptor>> mRevocableFds = new ArrayMap<>(); // Do not access this directly, instead use #getBlobFile(). private File mBlobFile; - BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { + BlobMetadata(Context context, long blobId, BlobHandle blobHandle) { mContext = context; this.mBlobId = blobId; this.mBlobHandle = blobHandle; - this.mUserId = userId; } long getBlobId() { @@ -122,10 +124,6 @@ class BlobMetadata { return mBlobHandle; } - int getUserId() { - return mUserId; - } - void addOrReplaceCommitter(@NonNull Committer committer) { synchronized (mMetadataLock) { // We need to override the committer data, so first remove any existing @@ -155,13 +153,24 @@ class BlobMetadata { } } - void removeCommittersFromUnknownPkgs(SparseArray<String> knownPackages) { + void removeCommittersFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) { synchronized (mMetadataLock) { - mCommitters.removeIf(committer -> - !committer.packageName.equals(knownPackages.get(committer.uid))); + mCommitters.removeIf(committer -> { + final int userId = UserHandle.getUserId(committer.uid); + final SparseArray<String> userPackages = knownPackages.get(userId); + if (userPackages == null) { + return true; + } + return !committer.packageName.equals(userPackages.get(committer.uid)); + }); } } + void addCommittersAndLeasees(BlobMetadata blobMetadata) { + mCommitters.addAll(blobMetadata.mCommitters); + mLeasees.addAll(blobMetadata.mLeasees); + } + @Nullable Committer getExistingCommitter(@NonNull String packageName, int uid) { synchronized (mCommitters) { @@ -201,10 +210,16 @@ class BlobMetadata { } } - void removeLeaseesFromUnknownPkgs(SparseArray<String> knownPackages) { + void removeLeaseesFromUnknownPkgs(SparseArray<SparseArray<String>> knownPackages) { synchronized (mMetadataLock) { - mLeasees.removeIf(leasee -> - !leasee.packageName.equals(knownPackages.get(leasee.uid))); + mLeasees.removeIf(leasee -> { + final int userId = UserHandle.getUserId(leasee.uid); + final SparseArray<String> userPackages = knownPackages.get(userId); + if (userPackages == null) { + return true; + } + return !leasee.packageName.equals(userPackages.get(leasee.uid)); + }); } } @@ -214,6 +229,25 @@ class BlobMetadata { } } + void removeDataForUser(int userId) { + synchronized (mMetadataLock) { + mCommitters.removeIf(committer -> (userId == UserHandle.getUserId(committer.uid))); + mLeasees.removeIf(leasee -> (userId == UserHandle.getUserId(leasee.uid))); + mRevocableFds.entrySet().removeIf(entry -> { + final Accessor accessor = entry.getKey(); + final ArraySet<RevocableFileDescriptor> rFds = entry.getValue(); + if (userId != UserHandle.getUserId(accessor.uid)) { + return false; + } + for (int i = 0, fdCount = rFds.size(); i < fdCount; ++i) { + rFds.valueAt(i).revoke(); + } + rFds.clear(); + return true; + }); + } + } + boolean hasValidLeases() { synchronized (mMetadataLock) { for (int i = 0, size = mLeasees.size(); i < size; ++i) { @@ -244,8 +278,12 @@ class BlobMetadata { } } + final int callingUserId = UserHandle.getUserId(callingUid); for (int i = 0, size = mCommitters.size(); i < size; ++i) { final Committer committer = mCommitters.valueAt(i); + if (callingUserId != UserHandle.getUserId(committer.uid)) { + continue; + } // Check if the caller is the same package that committed the blob. if (committer.equals(callingPackage, callingUid)) { @@ -259,38 +297,105 @@ class BlobMetadata { return true; } } + + final boolean canCallerAccessBlobsAcrossUsers = checkCallerCanAccessBlobsAcrossUsers( + callingPackage, callingUserId); + if (!canCallerAccessBlobsAcrossUsers) { + return false; + } + for (int i = 0, size = mCommitters.size(); i < size; ++i) { + final Committer committer = mCommitters.valueAt(i); + final int committerUserId = UserHandle.getUserId(committer.uid); + if (callingUserId == committerUserId) { + continue; + } + if (!checkCallerCanAccessBlobsAcrossUsers(callingPackage, committerUserId)) { + continue; + } + + // Check if the caller is allowed access as per the access mode specified + // by the committer. + if (committer.blobAccessMode.isAccessAllowedForCaller(mContext, + callingPackage, committer.packageName, callingUid, attributionTag)) { + return true; + } + } + + } + return false; + } + + private static boolean checkCallerCanAccessBlobsAcrossUsers( + String callingPackage, int callingUserId) { + return PermissionManager.checkPackageNamePermission(ACCESS_BLOBS_ACROSS_USERS, + callingPackage, callingUserId) == PackageManager.PERMISSION_GRANTED; + } + + boolean hasACommitterOrLeaseeInUser(int userId) { + return hasACommitterInUser(userId) || hasALeaseeInUser(userId); + } + + boolean hasACommitterInUser(int userId) { + synchronized (mMetadataLock) { + for (int i = 0, size = mCommitters.size(); i < size; ++i) { + final Committer committer = mCommitters.valueAt(i); + if (userId == UserHandle.getUserId(committer.uid)) { + return true; + } + } + } + return false; + } + + private boolean hasALeaseeInUser(int userId) { + synchronized (mMetadataLock) { + for (int i = 0, size = mLeasees.size(); i < size; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + if (userId == UserHandle.getUserId(leasee.uid)) { + return true; + } + } } return false; } boolean isACommitter(@NonNull String packageName, int uid) { synchronized (mMetadataLock) { - return isAnAccessor(mCommitters, packageName, uid); + return isAnAccessor(mCommitters, packageName, uid, UserHandle.getUserId(uid)); } } boolean isALeasee(@Nullable String packageName, int uid) { synchronized (mMetadataLock) { - final Leasee leasee = getAccessor(mLeasees, packageName, uid); + final Leasee leasee = getAccessor(mLeasees, packageName, uid, + UserHandle.getUserId(uid)); + return leasee != null && leasee.isStillValid(); + } + } + + private boolean isALeaseeInUser(@Nullable String packageName, int uid, int userId) { + synchronized (mMetadataLock) { + final Leasee leasee = getAccessor(mLeasees, packageName, uid, userId); return leasee != null && leasee.isStillValid(); } } private static <T extends Accessor> boolean isAnAccessor(@NonNull ArraySet<T> accessors, - @Nullable String packageName, int uid) { + @Nullable String packageName, int uid, int userId) { // Check if the package is an accessor of the data blob. - return getAccessor(accessors, packageName, uid) != null; + return getAccessor(accessors, packageName, uid, userId) != null; } private static <T extends Accessor> T getAccessor(@NonNull ArraySet<T> accessors, - @Nullable String packageName, int uid) { + @Nullable String packageName, int uid, int userId) { // Check if the package is an accessor of the data blob. for (int i = 0, size = accessors.size(); i < size; ++i) { final Accessor accessor = accessors.valueAt(i); if (packageName != null && uid != INVALID_UID && accessor.equals(packageName, uid)) { return (T) accessor; - } else if (packageName != null && accessor.packageName.equals(packageName)) { + } else if (packageName != null && accessor.packageName.equals(packageName) + && userId == UserHandle.getUserId(accessor.uid)) { return (T) accessor; } else if (uid != INVALID_UID && accessor.uid == uid) { return (T) accessor; @@ -299,23 +404,29 @@ class BlobMetadata { return null; } - boolean isALeasee(@NonNull String packageName) { - return isALeasee(packageName, INVALID_UID); - } - - boolean isALeasee(int uid) { - return isALeasee(null, uid); - } - - boolean hasOtherLeasees(@NonNull String packageName) { - return hasOtherLeasees(packageName, INVALID_UID); + boolean shouldAttributeToLeasee(@NonNull String packageName, int userId, + boolean callerHasStatsPermission) { + if (!isALeaseeInUser(packageName, INVALID_UID, userId)) { + return false; + } + if (!callerHasStatsPermission || !hasOtherLeasees(packageName, INVALID_UID, userId)) { + return true; + } + return false; } - boolean hasOtherLeasees(int uid) { - return hasOtherLeasees(null, uid); + boolean shouldAttributeToLeasee(int uid, boolean callerHasStatsPermission) { + final int userId = UserHandle.getUserId(uid); + if (!isALeaseeInUser(null, uid, userId)) { + return false; + } + if (!callerHasStatsPermission || !hasOtherLeasees(null, uid, userId)) { + return true; + } + return false; } - private boolean hasOtherLeasees(@Nullable String packageName, int uid) { + private boolean hasOtherLeasees(@Nullable String packageName, int uid, int userId) { synchronized (mMetadataLock) { for (int i = 0, size = mLeasees.size(); i < size; ++i) { final Leasee leasee = mLeasees.valueAt(i); @@ -326,7 +437,8 @@ class BlobMetadata { if (packageName != null && uid != INVALID_UID && !leasee.equals(packageName, uid)) { return true; - } else if (packageName != null && !leasee.packageName.equals(packageName)) { + } else if (packageName != null && (!leasee.packageName.equals(packageName) + || userId != UserHandle.getUserId(leasee.uid))) { return true; } else if (uid != INVALID_UID && leasee.uid != uid) { return true; @@ -371,7 +483,7 @@ class BlobMetadata { return mBlobFile; } - ParcelFileDescriptor openForRead(String callingPackage) throws IOException { + ParcelFileDescriptor openForRead(String callingPackage, int callingUid) throws IOException { // TODO: Add limit on opened fds FileDescriptor fd; try { @@ -381,7 +493,7 @@ class BlobMetadata { } try { if (BlobStoreConfig.shouldUseRevocableFdForReads()) { - return createRevocableFd(fd, callingPackage); + return createRevocableFd(fd, callingPackage, callingUid); } else { return new ParcelFileDescriptor(fd); } @@ -393,26 +505,28 @@ class BlobMetadata { @NonNull private ParcelFileDescriptor createRevocableFd(FileDescriptor fd, - String callingPackage) throws IOException { + String callingPackage, int callingUid) throws IOException { final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); + final Accessor accessor; synchronized (mRevocableFds) { - ArraySet<RevocableFileDescriptor> revocableFdsForPkg = - mRevocableFds.get(callingPackage); - if (revocableFdsForPkg == null) { - revocableFdsForPkg = new ArraySet<>(); - mRevocableFds.put(callingPackage, revocableFdsForPkg); + accessor = new Accessor(callingPackage, callingUid); + ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = + mRevocableFds.get(accessor); + if (revocableFdsForAccessor == null) { + revocableFdsForAccessor = new ArraySet<>(); + mRevocableFds.put(accessor, revocableFdsForAccessor); } - revocableFdsForPkg.add(revocableFd); + revocableFdsForAccessor.add(revocableFd); } revocableFd.addOnCloseListener((e) -> { synchronized (mRevocableFds) { - final ArraySet<RevocableFileDescriptor> revocableFdsForPkg = - mRevocableFds.get(callingPackage); - if (revocableFdsForPkg != null) { - revocableFdsForPkg.remove(revocableFd); - if (revocableFdsForPkg.isEmpty()) { - mRevocableFds.remove(callingPackage); + final ArraySet<RevocableFileDescriptor> revocableFdsForAccessor = + mRevocableFds.get(accessor); + if (revocableFdsForAccessor != null) { + revocableFdsForAccessor.remove(revocableFd); + if (revocableFdsForAccessor.isEmpty()) { + mRevocableFds.remove(accessor); } } } @@ -421,22 +535,23 @@ class BlobMetadata { } void destroy() { - revokeAllFds(); + revokeAndClearAllFds(); getBlobFile().delete(); } - private void revokeAllFds() { + private void revokeAndClearAllFds() { synchronized (mRevocableFds) { - for (int i = 0, pkgCount = mRevocableFds.size(); i < pkgCount; ++i) { - final ArraySet<RevocableFileDescriptor> packageFds = + for (int i = 0, accessorCount = mRevocableFds.size(); i < accessorCount; ++i) { + final ArraySet<RevocableFileDescriptor> rFds = mRevocableFds.valueAt(i); - if (packageFds == null) { + if (rFds == null) { continue; } - for (int j = 0, fdCount = packageFds.size(); j < fdCount; ++j) { - packageFds.valueAt(j).revoke(); + for (int j = 0, fdCount = rFds.size(); j < fdCount; ++j) { + rFds.valueAt(j).revoke(); } } + mRevocableFds.clear(); } } @@ -547,10 +662,10 @@ class BlobMetadata { fout.println("<empty>"); } else { for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { - final String packageName = mRevocableFds.keyAt(i); - final ArraySet<RevocableFileDescriptor> packageFds = + final Accessor accessor = mRevocableFds.keyAt(i); + final ArraySet<RevocableFileDescriptor> rFds = mRevocableFds.valueAt(i); - fout.println(packageName + "#" + packageFds.size()); + fout.println(accessor + ": #" + rFds.size()); } } fout.decreaseIndent(); @@ -560,7 +675,6 @@ class BlobMetadata { void writeToXml(XmlSerializer out) throws IOException { synchronized (mMetadataLock) { XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); - XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId); out.startTag(null, TAG_BLOB_HANDLE); mBlobHandle.writeToXml(out); @@ -584,7 +698,9 @@ class BlobMetadata { static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) throws XmlPullParserException, IOException { final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); - final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); + if (version < XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { + XmlUtils.readIntAttribute(in, ATTR_USER_ID); + } BlobHandle blobHandle = null; final ArraySet<Committer> committers = new ArraySet<>(); @@ -608,7 +724,7 @@ class BlobMetadata { return null; } - final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle, userId); + final BlobMetadata blobMetadata = new BlobMetadata(context, blobId, blobHandle); blobMetadata.setCommitters(committers); blobMetadata.setLeasees(leasees); return blobMetadata; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index 5cebf8d91cfc..502b29eb1a1f 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -27,12 +27,11 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.text.TextUtils; import android.util.DataUnit; +import android.util.IndentingPrintWriter; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; -import com.android.internal.util.IndentingPrintWriter; - import java.io.File; import java.util.concurrent.TimeUnit; @@ -47,8 +46,9 @@ class BlobStoreConfig { public static final int XML_VERSION_ADD_DESC_RES_NAME = 3; public static final int XML_VERSION_ADD_COMMIT_TIME = 4; public static final int XML_VERSION_ADD_SESSION_CREATION_TIME = 5; + public static final int XML_VERSION_ALLOW_ACCESS_ACROSS_USERS = 6; - public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME; + public static final int XML_VERSION_CURRENT = XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; public static final long INVALID_BLOB_ID = 0; public static final long INVALID_BLOB_SIZE = 0; diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 0e7354726123..cc5e31a91123 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -32,6 +32,7 @@ import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID; import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ALLOW_ACCESS_ACROSS_USERS; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs; import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs; @@ -83,6 +84,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.ExceptionUtils; +import android.util.IndentingPrintWriter; import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; @@ -96,7 +98,6 @@ import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; @@ -129,6 +130,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -146,9 +148,9 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") private long mCurrentMaxSessionId; - // Contains data of userId -> {BlobHandle -> {BlobMetadata}} + // Contains data of BlobHandle -> BlobMetadata. @GuardedBy("mBlobsLock") - private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>(); + private final ArrayMap<BlobHandle, BlobMetadata> mBlobsMap = new ArrayMap<>(); // Contains all ids that are currently in use. @GuardedBy("mBlobsLock") @@ -265,27 +267,24 @@ public class BlobStoreManagerService extends SystemService { return userSessions; } - @GuardedBy("mBlobsLock") - private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) { - ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId); - if (userBlobs == null) { - userBlobs = new ArrayMap<>(); - mBlobsMap.put(userId, userBlobs); + @VisibleForTesting + void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { + synchronized (mBlobsLock) { + mSessions.put(userId, userSessions); } - return userBlobs; } @VisibleForTesting - void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { + BlobMetadata getBlobForTest(BlobHandle blobHandle) { synchronized (mBlobsLock) { - mSessions.put(userId, userSessions); + return mBlobsMap.get(blobHandle); } } @VisibleForTesting - void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) { + int getBlobsCountForTest() { synchronized (mBlobsLock) { - mBlobsMap.put(userId, userBlobs); + return mBlobsMap.size(); } } @@ -319,14 +318,9 @@ public class BlobStoreManagerService extends SystemService { } @GuardedBy("mBlobsLock") - private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) { - addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId)); - } - - @GuardedBy("mBlobsLock") - private void addBlobForUserLocked(BlobMetadata blobMetadata, - ArrayMap<BlobHandle, BlobMetadata> userBlobs) { - userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata); + @VisibleForTesting + void addBlobLocked(BlobMetadata blobMetadata) { + mBlobsMap.put(blobMetadata.getBlobHandle(), blobMetadata); addActiveBlobIdLocked(blobMetadata.getBlobId()); } @@ -404,8 +398,7 @@ public class BlobStoreManagerService extends SystemService { private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid, String callingPackage, String attributionTag) throws IOException { synchronized (mBlobsLock) { - final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) - .get(blobHandle); + final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid, attributionTag)) { if (blobMetadata == null) { @@ -415,7 +408,7 @@ public class BlobStoreManagerService extends SystemService { } else { FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid, blobMetadata.getBlobId(), blobMetadata.getSize(), - FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED); + FrameworkStatsLog.BLOB_OPENED__RESULT__ACCESS_NOT_ALLOWED); } throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); @@ -425,7 +418,7 @@ public class BlobStoreManagerService extends SystemService { blobMetadata.getBlobId(), blobMetadata.getSize(), FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS); - return blobMetadata.openForRead(callingPackage); + return blobMetadata.openForRead(callingPackage, callingUid); } } @@ -433,11 +426,11 @@ public class BlobStoreManagerService extends SystemService { private int getCommittedBlobsCountLocked(int uid, String packageName) { // TODO: Maintain a counter instead of traversing all the blobs final AtomicInteger blobsCount = new AtomicInteger(0); - forEachBlobInUser((blobMetadata) -> { + forEachBlobLocked(blobMetadata -> { if (blobMetadata.isACommitter(packageName, uid)) { blobsCount.getAndIncrement(); } - }, UserHandle.getUserId(uid)); + }); return blobsCount.get(); } @@ -445,11 +438,11 @@ public class BlobStoreManagerService extends SystemService { private int getLeasedBlobsCountLocked(int uid, String packageName) { // TODO: Maintain a counter instead of traversing all the blobs final AtomicInteger blobsCount = new AtomicInteger(0); - forEachBlobInUser((blobMetadata) -> { + forEachBlobLocked(blobMetadata -> { if (blobMetadata.isALeasee(packageName, uid)) { blobsCount.getAndIncrement(); } - }, UserHandle.getUserId(uid)); + }); return blobsCount.get(); } @@ -465,8 +458,16 @@ public class BlobStoreManagerService extends SystemService { throw new LimitExceededException("Too many leased blobs for the caller: " + leasesCount); } - final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) - .get(blobHandle); + if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 + && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { + FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, + INVALID_BLOB_ID, INVALID_BLOB_SIZE, + FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); + throw new IllegalArgumentException( + "Lease expiry cannot be later than blobs expiry time"); + } + + final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid, attributionTag)) { if (blobMetadata == null) { @@ -481,15 +482,7 @@ public class BlobStoreManagerService extends SystemService { throw new SecurityException("Caller not allowed to access " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } - if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0 - && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) { - FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid, - blobMetadata.getBlobId(), blobMetadata.getSize(), - FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID); - throw new IllegalArgumentException( - "Lease expiry cannot be later than blobs expiry time"); - } if (blobMetadata.getSize() > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) { @@ -518,20 +511,18 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") long getTotalUsageBytesLocked(int callingUid, String callingPackage) { final AtomicLong totalBytes = new AtomicLong(0); - forEachBlobInUser((blobMetadata) -> { + forEachBlobLocked((blobMetadata) -> { if (blobMetadata.isALeasee(callingPackage, callingUid)) { totalBytes.getAndAdd(blobMetadata.getSize()); } - }, UserHandle.getUserId(callingUid)); + }); return totalBytes.get(); } private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid, String callingPackage, String attributionTag) { synchronized (mBlobsLock) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = - getUserBlobsLocked(UserHandle.getUserId(callingUid)); - final BlobMetadata blobMetadata = userBlobs.get(blobHandle); + final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid, attributionTag)) { throw new SecurityException("Caller not allowed to access " + blobHandle @@ -547,12 +538,12 @@ public class BlobStoreManagerService extends SystemService { synchronized (mBlobsLock) { // Check if blobMetadata object is still valid. If it is not, then // it means that it was already deleted and nothing else to do here. - if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) { + if (!Objects.equals(mBlobsMap.get(blobHandle), blobMetadata)) { return; } if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { deleteBlobLocked(blobMetadata); - userBlobs.remove(blobHandle); + mBlobsMap.remove(blobHandle); } writeBlobsInfoAsync(); } @@ -583,12 +574,18 @@ public class BlobStoreManagerService extends SystemService { } return packageResources; }; - getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> { + forEachBlobLocked((blobHandle, blobMetadata) -> { + if (!blobMetadata.hasACommitterOrLeaseeInUser(userId)) { + return; + } final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>(); blobMetadata.forEachLeasee(leasee -> { if (!leasee.isStillValid()) { return; } + if (userId != UserHandle.getUserId(leasee.uid)) { + return; + } final int descriptionResId = leasee.descriptionResEntryName == null ? Resources.ID_NULL : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName), @@ -608,9 +605,7 @@ public class BlobStoreManagerService extends SystemService { private void deleteBlobInternal(long blobId, int callingUid) { synchronized (mBlobsLock) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked( - UserHandle.getUserId(callingUid)); - userBlobs.entrySet().removeIf(entry -> { + mBlobsMap.entrySet().removeIf(entry -> { final BlobMetadata blobMetadata = entry.getValue(); if (blobMetadata.getBlobId() == blobId) { deleteBlobLocked(blobMetadata); @@ -625,19 +620,20 @@ public class BlobStoreManagerService extends SystemService { private List<BlobHandle> getLeasedBlobsInternal(int callingUid, @NonNull String callingPackage) { final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>(); - forEachBlobInUser(blobMetadata -> { - if (blobMetadata.isALeasee(callingPackage, callingUid)) { - leasedBlobs.add(blobMetadata.getBlobHandle()); - } - }, UserHandle.getUserId(callingUid)); + synchronized (mBlobsLock) { + forEachBlobLocked(blobMetadata -> { + if (blobMetadata.isALeasee(callingPackage, callingUid)) { + leasedBlobs.add(blobMetadata.getBlobHandle()); + } + }); + } return leasedBlobs; } private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle, int callingUid, @NonNull String callingPackage, String attributionTag) { synchronized (mBlobsLock) { - final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) - .get(blobHandle); + final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller( callingPackage, callingUid, attributionTag)) { throw new SecurityException("Caller not allowed to access " + blobHandle @@ -699,14 +695,14 @@ public class BlobStoreManagerService extends SystemService { FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED); break; } - final int userId = UserHandle.getUserId(session.getOwnerUid()); - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked( - userId); - BlobMetadata blob = userBlobs.get(session.getBlobHandle()); - if (blob == null) { + final BlobMetadata blob; + final int blobIndex = mBlobsMap.indexOfKey(session.getBlobHandle()); + if (blobIndex >= 0) { + blob = mBlobsMap.valueAt(blobIndex); + } else { blob = new BlobMetadata(mContext, session.getSessionId(), - session.getBlobHandle(), userId); - addBlobForUserLocked(blob, userBlobs); + session.getBlobHandle()); + addBlobLocked(blob); } final Committer existingCommitter = blob.getExistingCommitter( session.getOwnerPackageName(), session.getOwnerUid()); @@ -738,7 +734,7 @@ public class BlobStoreManagerService extends SystemService { // But if it is a recommit, just leave it as is. if (session.getSessionId() == blob.getBlobId()) { deleteBlobLocked(blob); - userBlobs.remove(blob.getBlobHandle()); + mBlobsMap.remove(blob.getBlobHandle()); } } // Delete redundant data from recommits. @@ -874,13 +870,10 @@ public class BlobStoreManagerService extends SystemService { out.startTag(null, TAG_BLOBS); XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - out.startTag(null, TAG_BLOB); - userBlobs.valueAt(j).writeToXml(out); - out.endTag(null, TAG_BLOB); - } + for (int i = 0, count = mBlobsMap.size(); i < count; ++i) { + out.startTag(null, TAG_BLOB); + mBlobsMap.valueAt(i).writeToXml(out); + out.endTag(null, TAG_BLOB); } out.endTag(null, TAG_BLOBS); @@ -925,16 +918,21 @@ public class BlobStoreManagerService extends SystemService { if (TAG_BLOB.equals(in.getName())) { final BlobMetadata blobMetadata = BlobMetadata.createFromXml( in, version, mContext); - final SparseArray<String> userPackages = allPackages.get( - blobMetadata.getUserId()); - if (userPackages == null) { - blobMetadata.getBlobFile().delete(); + blobMetadata.removeCommittersFromUnknownPkgs(allPackages); + blobMetadata.removeLeaseesFromUnknownPkgs(allPackages); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); + if (version >= XML_VERSION_ALLOW_ACCESS_ACROSS_USERS) { + addBlobLocked(blobMetadata); } else { - addBlobForUserLocked(blobMetadata, blobMetadata.getUserId()); - blobMetadata.removeCommittersFromUnknownPkgs(userPackages); - blobMetadata.removeLeaseesFromUnknownPkgs(userPackages); + final BlobMetadata existingBlobMetadata = mBlobsMap.get( + blobMetadata.getBlobHandle()); + if (existingBlobMetadata == null) { + addBlobLocked(blobMetadata); + } else { + existingBlobMetadata.addCommittersAndLeasees(blobMetadata); + blobMetadata.getBlobFile().delete(); + } } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); } } if (LOGV) { @@ -977,14 +975,6 @@ public class BlobStoreManagerService extends SystemService { } } - private int getPackageUid(String packageName, int userId) { - final int uid = mPackageManagerInternal.getPackageUid( - packageName, - MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES, - userId); - return uid; - } - private SparseArray<SparseArray<String>> getAllPackages() { final SparseArray<SparseArray<String>> allPackages = new SparseArray<>(); final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds(); @@ -1004,7 +994,7 @@ public class BlobStoreManagerService extends SystemService { return allPackages; } - AtomicFile prepareSessionsIndexFile() { + private AtomicFile prepareSessionsIndexFile() { final File file = BlobStoreConfig.prepareSessionIndexFile(); if (file == null) { return null; @@ -1012,7 +1002,7 @@ public class BlobStoreManagerService extends SystemService { return new AtomicFile(file, "session_index" /* commitLogTag */); } - AtomicFile prepareBlobsIndexFile() { + private AtomicFile prepareBlobsIndexFile() { final File file = BlobStoreConfig.prepareBlobsIndexFile(); if (file == null) { return null; @@ -1037,9 +1027,7 @@ public class BlobStoreManagerService extends SystemService { writeBlobSessionsAsync(); // Remove the package from the committer and leasee list - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = - getUserBlobsLocked(UserHandle.getUserId(uid)); - userBlobs.entrySet().removeIf(entry -> { + mBlobsMap.entrySet().removeIf(entry -> { final BlobMetadata blobMetadata = entry.getValue(); final boolean isACommitter = blobMetadata.isACommitter(packageName, uid); if (isACommitter) { @@ -1074,14 +1062,15 @@ public class BlobStoreManagerService extends SystemService { } } - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = - mBlobsMap.removeReturnOld(userId); - if (userBlobs != null) { - for (int i = 0, count = userBlobs.size(); i < count; ++i) { - final BlobMetadata blobMetadata = userBlobs.valueAt(i); + mBlobsMap.entrySet().removeIf(entry -> { + final BlobMetadata blobMetadata = entry.getValue(); + blobMetadata.removeDataForUser(userId); + if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { deleteBlobLocked(blobMetadata); + return true; } - } + return false; + }); if (LOGV) { Slog.v(TAG, "Removed blobs data in user " + userId); } @@ -1114,22 +1103,19 @@ public class BlobStoreManagerService extends SystemService { } // Cleanup any stale blobs. - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - userBlobs.entrySet().removeIf(entry -> { - final BlobMetadata blobMetadata = entry.getValue(); + mBlobsMap.entrySet().removeIf(entry -> { + final BlobMetadata blobMetadata = entry.getValue(); - // Remove expired leases - blobMetadata.removeExpiredLeases(); + // Remove expired leases + blobMetadata.removeExpiredLeases(); - if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { - deleteBlobLocked(blobMetadata); - deletedBlobIds.add(blobMetadata.getBlobId()); - return true; - } - return false; - }); - } + if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) { + deleteBlobLocked(blobMetadata); + deletedBlobIds.add(blobMetadata.getBlobId()); + return true; + } + return false; + }); writeBlobsInfoAsync(); // Cleanup any stale sessions. @@ -1195,34 +1181,34 @@ public class BlobStoreManagerService extends SystemService { void runClearAllBlobs(@UserIdInt int userId) { synchronized (mBlobsLock) { - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final int blobUserId = mBlobsMap.keyAt(i); - if (userId != UserHandle.USER_ALL && userId != blobUserId) { - continue; + mBlobsMap.entrySet().removeIf(entry -> { + final BlobMetadata blobMetadata = entry.getValue(); + if (userId == UserHandle.USER_ALL) { + mActiveBlobIds.remove(blobMetadata.getBlobId()); + return true; } - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId()); + blobMetadata.removeDataForUser(userId); + if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { + mActiveBlobIds.remove(blobMetadata.getBlobId()); + return true; } - } - if (userId == UserHandle.USER_ALL) { - mBlobsMap.clear(); - } else { - mBlobsMap.remove(userId); - } + return false; + }); writeBlobsInfoAsync(); } } void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) { synchronized (mBlobsLock) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); - final BlobMetadata blobMetadata = userBlobs.get(blobHandle); + final BlobMetadata blobMetadata = mBlobsMap.get(blobHandle); if (blobMetadata == null) { return; } - deleteBlobLocked(blobMetadata); - userBlobs.remove(blobHandle); + blobMetadata.removeDataForUser(userId); + if (blobMetadata.shouldBeDeleted(false /* respectLeaseWaitTime */)) { + deleteBlobLocked(blobMetadata); + mBlobsMap.remove(blobHandle); + } writeBlobsInfoAsync(); } } @@ -1235,11 +1221,12 @@ public class BlobStoreManagerService extends SystemService { boolean isBlobAvailable(long blobId, int userId) { synchronized (mBlobsLock) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); - for (BlobMetadata blobMetadata : userBlobs.values()) { - if (blobMetadata.getBlobId() == blobId) { - return true; + for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { + final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); + if (blobMetadata.getBlobId() != blobId) { + continue; } + return blobMetadata.hasACommitterInUser(userId); } return false; } @@ -1274,27 +1261,22 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final int userId = mBlobsMap.keyAt(i); - if (!dumpArgs.shouldDumpUser(userId)) { + fout.println("List of blobs (" + mBlobsMap.size() + "):"); + fout.increaseIndent(); + for (int i = 0, blobCount = mBlobsMap.size(); i < blobCount; ++i) { + final BlobMetadata blobMetadata = mBlobsMap.valueAt(i); + if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { continue; } - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - fout.println("List of blobs in user #" - + userId + " (" + userBlobs.size() + "):"); + fout.println("Blob #" + blobMetadata.getBlobId()); fout.increaseIndent(); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - final BlobMetadata blobMetadata = userBlobs.valueAt(j); - if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { - continue; - } - fout.println("Blob #" + blobMetadata.getBlobId()); - fout.increaseIndent(); - blobMetadata.dump(fout, dumpArgs); - fout.decreaseIndent(); - } + blobMetadata.dump(fout, dumpArgs); fout.decreaseIndent(); } + if (mBlobsMap.isEmpty()) { + fout.println("<empty>"); + } + fout.decreaseIndent(); } private class BlobStorageStatsAugmenter implements StorageStatsAugmenter { @@ -1308,13 +1290,12 @@ public class BlobStoreManagerService extends SystemService { } }, userId); - forEachBlobInUser(blobMetadata -> { - if (blobMetadata.isALeasee(packageName)) { - if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) { - blobsDataSize.getAndAdd(blobMetadata.getSize()); - } + forEachBlob(blobMetadata -> { + if (blobMetadata.shouldAttributeToLeasee(packageName, userId, + callerHasStatsPermission)) { + blobsDataSize.getAndAdd(blobMetadata.getSize()); } - }, userId); + }); stats.dataSize += blobsDataSize.get(); } @@ -1330,13 +1311,12 @@ public class BlobStoreManagerService extends SystemService { } }, userId); - forEachBlobInUser(blobMetadata -> { - if (blobMetadata.isALeasee(uid)) { - if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) { - blobsDataSize.getAndAdd(blobMetadata.getSize()); - } + forEachBlob(blobMetadata -> { + if (blobMetadata.shouldAttributeToLeasee(uid, + callerHasStatsPermission)) { + blobsDataSize.getAndAdd(blobMetadata.getSize()); } - }, userId); + }); stats.dataSize += blobsDataSize.get(); } @@ -1352,13 +1332,26 @@ public class BlobStoreManagerService extends SystemService { } } - private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) { - synchronized (mBlobsLock) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); - for (int i = 0, count = userBlobs.size(); i < count; ++i) { - final BlobMetadata blobMetadata = userBlobs.valueAt(i); - consumer.accept(blobMetadata); - } + private void forEachBlob(Consumer<BlobMetadata> consumer) { + synchronized (mBlobsMap) { + forEachBlobLocked(consumer); + } + } + + @GuardedBy("mBlobsMap") + private void forEachBlobLocked(Consumer<BlobMetadata> consumer) { + for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { + final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); + consumer.accept(blobMetadata); + } + } + + @GuardedBy("mBlobsMap") + private void forEachBlobLocked(BiConsumer<BlobHandle, BlobMetadata> consumer) { + for (int blobIdx = 0, count = mBlobsMap.size(); blobIdx < count; ++blobIdx) { + final BlobHandle blobHandle = mBlobsMap.keyAt(blobIdx); + final BlobMetadata blobMetadata = mBlobsMap.valueAt(blobIdx); + consumer.accept(blobHandle, blobMetadata); } } @@ -1886,15 +1879,7 @@ public class BlobStoreManagerService extends SystemService { } private int pullBlobData(int atomTag, List<StatsEvent> data) { - synchronized (mBlobsLock) { - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - final BlobMetadata blob = userBlobs.valueAt(j); - data.add(blob.dumpAsStatsEvent(atomTag)); - } - } - } + forEachBlob(blobMetadata -> data.add(blobMetadata.dumpAsStatsEvent(atomTag))); return StatsManager.PULL_SUCCESS; } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 2c3f682a46e0..3f0032fe537e 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -56,12 +56,12 @@ import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.util.ExceptionUtils; +import android.util.IndentingPrintWriter; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.blob.BlobStoreManagerService.DumpArgs; diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java index 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..03d9a968f790 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; @@ -1683,17 +1684,23 @@ public class AlarmManagerService extends SystemService { } } - if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) { - // Do not support windows for idle-until alarms. - windowLength = AlarmManager.WINDOW_EXACT; - } - - // Sanity check the window length. This will catch people mistakenly - // trying to pass an end-of-window timestamp rather than a duration. - if (windowLength > AlarmManager.INTERVAL_HALF_DAY) { + // Snap the window to reasonable limits. + if (windowLength > INTERVAL_DAY) { Slog.w(TAG, "Window length " + windowLength - + "ms suspiciously long; limiting to 1 hour"); - windowLength = AlarmManager.INTERVAL_HOUR; + + "ms suspiciously long; limiting to 1 day"); + windowLength = INTERVAL_DAY; + } else if (windowLength > 0 && windowLength < mConstants.MIN_WINDOW + && (flags & FLAG_PRIORITIZE) == 0) { + if (CompatChanges.isChangeEnabled(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS, + callingPackage, UserHandle.getUserHandleForUid(callingUid))) { + Slog.w(TAG, "Window length " + windowLength + "ms too short; expanding to " + + mConstants.MIN_WINDOW + "ms."); + windowLength = mConstants.MIN_WINDOW; + } else { + // TODO (b/185199076): Remove log once we have some data about what apps will break + Slog.wtf(TAG, "Short window " + windowLength + "ms specified by " + + callingPackage); + } } // Sanity check the recurrence interval. This will catch people who supply @@ -1730,14 +1737,13 @@ public class AlarmManagerService extends SystemService { final long triggerElapsed = (nominalTrigger > minTrigger) ? nominalTrigger : minTrigger; final long maxElapsed; - if (windowLength == AlarmManager.WINDOW_EXACT) { + if (windowLength == 0) { maxElapsed = triggerElapsed; } else if (windowLength < 0) { maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval); // Fix this window in place, so that as time approaches we don't collapse it. windowLength = maxElapsed - triggerElapsed; } else { - windowLength = Math.max(windowLength, mConstants.MIN_WINDOW); maxElapsed = triggerElapsed + windowLength; } synchronized (mLock) { @@ -2135,17 +2141,63 @@ public class AlarmManagerService extends SystemService { + " does not belong to the calling uid " + callingUid); } + // Repeating alarms must use PendingIntent, not direct listener + if (interval != 0 && directReceiver != null) { + throw new IllegalArgumentException("Repeating alarms cannot use AlarmReceivers"); + } + + if (workSource != null) { + getContext().enforcePermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, + Binder.getCallingPid(), callingUid, "AlarmManager.set"); + } + + if ((flags & AlarmManager.FLAG_IDLE_UNTIL) != 0) { + // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm + // manager when to come out of idle mode, which is only for DeviceIdleController. + if (callingUid != Process.SYSTEM_UID) { + // TODO (b/169463012): Throw instead of tolerating this mistake. + flags &= ~AlarmManager.FLAG_IDLE_UNTIL; + } else { + // Do not support windows for idle-until alarms. + windowLength = 0; + } + } + + // Remove flags reserved for the service, we will apply those later as appropriate. + flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED + | FLAG_ALLOW_WHILE_IDLE_COMPAT); + + // If this alarm is for an alarm clock, then it must be exact and we will + // use it to wake early from idle if needed. + if (alarmClock != null) { + flags |= FLAG_WAKE_FROM_IDLE; + windowLength = 0; + + // If the caller is a core system component or on the user's allowlist, and not calling + // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. + // This means we will allow these alarms to go off as normal even while idle, with no + // timing restrictions. + } else if (workSource == null && (UserHandle.isCore(callingUid) + || UserHandle.isSameApp(callingUid, mSystemUiUid) + || ((mAppStateTracker != null) + && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { + flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; + flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_PRIORITIZE); + } + final boolean allowWhileIdle = (flags & FLAG_ALLOW_WHILE_IDLE) != 0; - final boolean exact = (windowLength == AlarmManager.WINDOW_EXACT); + final boolean exact = (windowLength == 0); - // make sure the caller is allowed to use the requested kind of alarm, and also + // Make sure the caller is allowed to use the requested kind of alarm, and also // decide what quota and broadcast options to use. Bundle idleOptions = null; if ((flags & FLAG_PRIORITIZE) != 0) { getContext().enforcePermission( Manifest.permission.SCHEDULE_PRIORITIZED_ALARM, Binder.getCallingPid(), callingUid, "AlarmManager.setPrioritized"); - flags &= ~(FLAG_ALLOW_WHILE_IDLE | FLAG_ALLOW_WHILE_IDLE_COMPAT); + // The API doesn't allow using both together. + flags &= ~FLAG_ALLOW_WHILE_IDLE; } else if (exact || allowWhileIdle) { final boolean needsPermission; boolean lowerQuota; @@ -2183,55 +2235,11 @@ public class AlarmManagerService extends SystemService { } } - // Repeating alarms must use PendingIntent, not direct listener - if (interval != 0) { - if (directReceiver != null) { - throw new IllegalArgumentException( - "Repeating alarms cannot use AlarmReceivers"); - } - } - - if (workSource != null) { - getContext().enforcePermission( - android.Manifest.permission.UPDATE_DEVICE_STATS, - Binder.getCallingPid(), callingUid, "AlarmManager.set"); - } - - // No incoming callers can request either WAKE_FROM_IDLE or - // ALLOW_WHILE_IDLE_UNRESTRICTED -- we will apply those later as appropriate. - flags &= ~(FLAG_WAKE_FROM_IDLE | FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED); - - // Only the system can use FLAG_IDLE_UNTIL -- this is used to tell the alarm - // manager when to come out of idle mode, which is only for DeviceIdleController. - if (callingUid != Process.SYSTEM_UID) { - flags &= ~AlarmManager.FLAG_IDLE_UNTIL; - } - // If this is an exact time alarm, then it can't be batched with other alarms. - if (windowLength == AlarmManager.WINDOW_EXACT) { + if (exact) { flags |= AlarmManager.FLAG_STANDALONE; } - // If this alarm is for an alarm clock, then it must be standalone and we will - // use it to wake early from idle if needed. - if (alarmClock != null) { - flags |= FLAG_WAKE_FROM_IDLE | AlarmManager.FLAG_STANDALONE; - - // If the caller is a core system component or on the user's whitelist, and not calling - // to do work on behalf of someone else, then always set ALLOW_WHILE_IDLE_UNRESTRICTED. - // This means we will allow these alarms to go off as normal even while idle, with no - // timing restrictions. - } else if (workSource == null && (UserHandle.isCore(callingUid) - || UserHandle.isSameApp(callingUid, mSystemUiUid) - || ((mAppStateTracker != null) - && mAppStateTracker.isUidPowerSaveUserExempt(callingUid)))) { - flags |= FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; - flags &= ~FLAG_ALLOW_WHILE_IDLE; - flags &= ~FLAG_ALLOW_WHILE_IDLE_COMPAT; - flags &= ~FLAG_PRIORITIZE; - idleOptions = null; - } - setImpl(type, triggerAtTime, windowLength, interval, operation, directReceiver, listenerTag, flags, workSource, alarmClock, callingUid, callingPackage, idleOptions); diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java index c37d2c36b068..9b1b06658a98 100644 --- a/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java +++ b/apex/jobscheduler/service/java/com/android/server/alarm/LazyAlarmStore.java @@ -16,7 +16,6 @@ package com.android.server.alarm; -import static com.android.server.alarm.AlarmManagerService.TAG; import static com.android.server.alarm.AlarmManagerService.dumpAlarmList; import static com.android.server.alarm.AlarmManagerService.isTimeTickAlarm; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index d94d638a7021..131783f9e04e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -16,6 +16,7 @@ package com.android.server.job; +import static com.android.server.job.JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.annotation.IntDef; @@ -39,7 +40,9 @@ import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; +import android.util.Pools; import android.util.Slog; +import android.util.SparseArrayMap; import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; @@ -60,6 +63,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.List; +import java.util.function.Consumer; /** * This class decides, given the various configuration and the system status, which jobs can start @@ -73,6 +77,12 @@ class JobConcurrencyManager { private static final String KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS = CONFIG_KEY_PREFIX_CONCURRENCY + "screen_off_adjustment_delay_ms"; private static final long DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS = 30_000; + private static final String KEY_PKG_CONCURRENCY_LIMIT_EJ = + CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_ej"; + private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_EJ = 3; + private static final String KEY_PKG_CONCURRENCY_LIMIT_REGULAR = + CONFIG_KEY_PREFIX_CONCURRENCY + "pkg_concurrency_limit_regular"; + private static final int DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR = MAX_JOB_CONTEXTS_COUNT / 2; /** * Set of possible execution types that a job can have. The actual type(s) of a job are based @@ -165,8 +175,6 @@ class JobConcurrencyManager { private long mLastScreenOnRealtime; private long mLastScreenOffRealtime; - private static final int MAX_JOB_CONTEXTS_COUNT = JobSchedulerService.MAX_JOB_CONTEXTS_COUNT; - private static final WorkConfigLimitsPerMemoryTrimLevel CONFIG_LIMITS_SCREEN_ON = new WorkConfigLimitsPerMemoryTrimLevel( new WorkTypeConfig("screen_on_normal", 11, @@ -274,11 +282,28 @@ class JobConcurrencyManager { private final WorkCountTracker mWorkCountTracker = new WorkCountTracker(); + private final Pools.Pool<PackageStats> mPkgStatsPool = + new Pools.SimplePool<>(MAX_JOB_CONTEXTS_COUNT); + + private final SparseArrayMap<String, PackageStats> mActivePkgStats = new SparseArrayMap<>(); + private WorkTypeConfig mWorkTypeConfig = CONFIG_LIMITS_SCREEN_OFF.normal; /** Wait for this long after screen off before adjusting the job concurrency. */ private long mScreenOffAdjustmentDelayMs = DEFAULT_SCREEN_OFF_ADJUSTMENT_DELAY_MS; + /** + * The maximum number of expedited jobs a single userId-package can have running simultaneously. + * TOP apps are not limited. + */ + private long mPkgConcurrencyLimitEj = DEFAULT_PKG_CONCURRENCY_LIMIT_EJ; + + /** + * The maximum number of regular jobs a single userId-package can have running simultaneously. + * TOP apps are not limited. + */ + private long mPkgConcurrencyLimitRegular = DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR; + /** Current memory trim level. */ private int mLastMemoryTrimLevel; @@ -286,6 +311,9 @@ class JobConcurrencyManager { private long mNextSystemStateRefreshTime; private static final int SYSTEM_STATE_REFRESH_MIN_INTERVAL = 1000; + private final Consumer<PackageStats> mPackageStatsStagingCountClearer = + PackageStats::resetStagedCount; + private final StatLogger mStatLogger = new StatLogger(new String[]{ "assignJobsToContexts", "refreshSystemState", @@ -330,6 +358,21 @@ class JobConcurrencyManager { onInteractiveStateChanged(mPowerManager.isInteractive()); } + @GuardedBy("mLock") + void onAppRemovedLocked(String pkgName, int uid) { + final PackageStats packageStats = mActivePkgStats.get(UserHandle.getUserId(uid), pkgName); + if (packageStats != null) { + if (packageStats.numRunningEj > 0 || packageStats.numRunningRegular > 0) { + // Don't delete the object just yet. We'll remove it in onJobCompleted() when the + // jobs officially stop running. + Slog.w(TAG, + pkgName + "(" + uid + ") marked as removed before jobs stopped running"); + } else { + mActivePkgStats.delete(UserHandle.getUserId(uid), pkgName); + } + } + } + void onUserRemoved(int userId) { mGracePeriodObserver.onUserRemoved(userId); } @@ -557,6 +600,7 @@ class JobConcurrencyManager { boolean startingJob = false; int preemptReasonCode = JobParameters.STOP_REASON_UNDEFINED; String preemptReason = null; + final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); // TODO(141645789): rewrite this to look at empty contexts first so we don't // unnecessarily preempt for (int j = 0; j < MAX_JOB_CONTEXTS_COUNT; j++) { @@ -566,7 +610,7 @@ class JobConcurrencyManager { final boolean preferredUidOkay = (preferredUid == nextPending.getUid()) || (preferredUid == JobServiceContext.NO_PREFERRED_UID); - if (preferredUidOkay && workType != WORK_TYPE_NONE) { + if (preferredUidOkay && pkgConcurrencyOkay && workType != WORK_TYPE_NONE) { // This slot is free, and we haven't yet hit the limit on // concurrent jobs... we can just throw the job in to here. selectedContextId = j; @@ -579,9 +623,11 @@ class JobConcurrencyManager { continue; } if (job.getUid() != nextPending.getUid()) { - // Maybe stop the job if it has had its day in the sun. + // Maybe stop the job if it has had its day in the sun. Don't let a different + // app preempt jobs started for TOP apps though. final String reason = shouldStopJobReason[j]; - if (reason != null && mWorkCountTracker.canJobStart(allWorkTypes, + if (job.lastEvaluatedPriority < JobInfo.PRIORITY_TOP_APP + && reason != null && mWorkCountTracker.canJobStart(allWorkTypes, activeServices.get(j).getRunningJobWorkType()) != WORK_TYPE_NONE) { // Right now, the way the code is set up, we don't need to explicitly // assign the new job to this context since we'll reassign when the @@ -608,23 +654,27 @@ class JobConcurrencyManager { // actually starting a job, so don't set startingJob. } } + final PackageStats packageStats = getPkgStatsLocked( + nextPending.getSourceUserId(), nextPending.getSourcePackageName()); if (selectedContextId != -1) { contextIdToJobMap[selectedContextId] = nextPending; slotChanged[selectedContextId] = true; preemptReasonCodeForContext[selectedContextId] = preemptReasonCode; preemptReasonForContext[selectedContextId] = preemptReason; + packageStats.adjustStagedCount(true, nextPending.shouldTreatAsExpeditedJob()); } if (startingJob) { // Increase the counters when we're going to start a job. workTypeForContext[selectedContextId] = workType; mWorkCountTracker.stageJob(workType, allWorkTypes); + mActivePkgStats.add( + nextPending.getSourceUserId(), nextPending.getSourcePackageName(), + packageStats); } } if (DEBUG) { Slog.d(TAG, printContextIdToJobMap(contextIdToJobMap, "running jobs final")); - } - if (DEBUG) { Slog.d(TAG, "assignJobsToContexts: " + mWorkCountTracker.toString()); } @@ -660,6 +710,7 @@ class JobConcurrencyManager { } } mWorkCountTracker.resetStagingCount(); + mActivePkgStats.forEach(mPackageStatsStagingCountClearer); noteConcurrency(); } @@ -702,18 +753,66 @@ class JobConcurrencyManager { } @GuardedBy("mLock") + @NonNull + private PackageStats getPkgStatsLocked(int userId, @NonNull String packageName) { + PackageStats packageStats = mActivePkgStats.get(userId, packageName); + if (packageStats == null) { + packageStats = mPkgStatsPool.acquire(); + if (packageStats == null) { + packageStats = new PackageStats(); + } + packageStats.setPackage(userId, packageName); + } + return packageStats; + } + + @GuardedBy("mLock") + private boolean isPkgConcurrencyLimitedLocked(@NonNull JobStatus jobStatus) { + if (jobStatus.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) { + // Don't restrict top apps' concurrency. The work type limits will make sure + // background jobs have slots to run if the system has resources. + return false; + } + // Use < instead of <= as that gives us a little wiggle room in case a new job comes + // along very shortly. + if (mService.mPendingJobs.size() + mRunningJobs.size() < mWorkTypeConfig.getMaxTotal()) { + // Don't artificially limit a single package if we don't even have enough jobs to use + // the maximum number of slots. We'll preempt the job later if we need the slot. + return false; + } + final PackageStats packageStats = + mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + if (packageStats == null) { + // No currently running jobs. + return false; + } + if (jobStatus.shouldTreatAsExpeditedJob()) { + return packageStats.numRunningEj + packageStats.numStagedEj < mPkgConcurrencyLimitEj; + } else { + return packageStats.numRunningRegular + packageStats.numStagedRegular + < mPkgConcurrencyLimitRegular; + } + } + + @GuardedBy("mLock") private void startJobLocked(@NonNull JobServiceContext worker, @NonNull JobStatus jobStatus, @WorkType final int workType) { final List<StateController> controllers = mService.mControllers; for (int ic = 0; ic < controllers.size(); ic++) { controllers.get(ic).prepareForExecutionLocked(jobStatus); } + final PackageStats packageStats = + getPkgStatsLocked(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + packageStats.adjustStagedCount(false, jobStatus.shouldTreatAsExpeditedJob()); if (!worker.executeRunnableJob(jobStatus, workType)) { Slog.e(TAG, "Error executing " + jobStatus); mWorkCountTracker.onStagedJobFailed(workType); } else { mRunningJobs.add(jobStatus); mWorkCountTracker.onJobStarted(workType); + packageStats.adjustRunningCount(true, jobStatus.shouldTreatAsExpeditedJob()); + mActivePkgStats.add( + jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), packageStats); } final List<JobStatus> pendingJobs = mService.mPendingJobs; if (pendingJobs.remove(jobStatus)) { @@ -726,6 +825,18 @@ class JobConcurrencyManager { @WorkType final int workType) { mWorkCountTracker.onJobFinished(workType); mRunningJobs.remove(jobStatus); + final PackageStats packageStats = + mActivePkgStats.get(jobStatus.getSourceUserId(), jobStatus.getSourcePackageName()); + if (packageStats == null) { + Slog.wtf(TAG, "Running job didn't have an active PackageStats object"); + } else { + packageStats.adjustRunningCount(false, jobStatus.startedAsExpeditedJob); + if (packageStats.numRunningEj <= 0 && packageStats.numRunningRegular <= 0) { + mActivePkgStats.delete(packageStats.userId, packageStats.packageName); + mPkgStatsPool.release(packageStats); + } + } + final List<JobStatus> pendingJobs = mService.mPendingJobs; if (worker.getPreferredUid() != JobServiceContext.NO_PREFERRED_UID) { updateCounterConfigLocked(); @@ -746,7 +857,7 @@ class JobConcurrencyManager { } if (worker.getPreferredUid() != nextPending.getUid()) { - if (backupJob == null) { + if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { int allWorkTypes = getJobWorkTypes(nextPending); int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType != WORK_TYPE_NONE) { @@ -758,6 +869,13 @@ class JobConcurrencyManager { continue; } + // Only bypass the concurrent limit if we had preempted the job due to a higher + // priority job. + if (nextPending.lastEvaluatedPriority <= jobStatus.lastEvaluatedPriority + && isPkgConcurrencyLimitedLocked(nextPending)) { + continue; + } + if (highestPriorityJob == null || highestPriorityJob.lastEvaluatedPriority < nextPending.lastEvaluatedPriority) { @@ -815,6 +933,10 @@ class JobConcurrencyManager { continue; } + if (isPkgConcurrencyLimitedLocked(nextPending)) { + continue; + } + final int allWorkTypes = getJobWorkTypes(nextPending); final int workAsType = mWorkCountTracker.canJobStart(allWorkTypes); if (workAsType == WORK_TYPE_NONE) { @@ -979,8 +1101,16 @@ class JobConcurrencyManager { CONFIG_LIMITS_SCREEN_OFF.moderate.update(properties); CONFIG_LIMITS_SCREEN_OFF.low.update(properties); CONFIG_LIMITS_SCREEN_OFF.critical.update(properties); + + // Package concurrency limits must in the range [1, MAX_JOB_CONTEXTS_COUNT]. + mPkgConcurrencyLimitEj = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, + properties.getInt(KEY_PKG_CONCURRENCY_LIMIT_EJ, DEFAULT_PKG_CONCURRENCY_LIMIT_EJ))); + mPkgConcurrencyLimitRegular = Math.max(1, Math.min(MAX_JOB_CONTEXTS_COUNT, + properties.getInt( + KEY_PKG_CONCURRENCY_LIMIT_REGULAR, DEFAULT_PKG_CONCURRENCY_LIMIT_REGULAR))); } + @GuardedBy("mLock") public void dumpLocked(IndentingPrintWriter pw, long now, long nowRealtime) { pw.println("Concurrency:"); @@ -989,6 +1119,8 @@ class JobConcurrencyManager { pw.println("Configuration:"); pw.increaseIndent(); pw.print(KEY_SCREEN_OFF_ADJUSTMENT_DELAY_MS, mScreenOffAdjustmentDelayMs).println(); + pw.print(KEY_PKG_CONCURRENCY_LIMIT_EJ, mPkgConcurrencyLimitEj).println(); + pw.print(KEY_PKG_CONCURRENCY_LIMIT_REGULAR, mPkgConcurrencyLimitRegular).println(); pw.println(); CONFIG_LIMITS_SCREEN_ON.normal.dump(pw); pw.println(); @@ -1033,6 +1165,12 @@ class JobConcurrencyManager { pw.println(mLastMemoryTrimLevel); pw.println(); + pw.println("Active Package stats:"); + pw.increaseIndent(); + mActivePkgStats.forEach(pkgStats -> pkgStats.dumpLocked(pw)); + pw.decreaseIndent(); + pw.println(); + pw.print("User Grace Period: "); pw.println(mGracePeriodObserver.mGracePeriodExpiration); pw.println(); @@ -1620,4 +1758,53 @@ class JobConcurrencyManager { return sb.toString(); } } + + private static class PackageStats { + public int userId; + public String packageName; + public int numRunningEj; + public int numRunningRegular; + public int numStagedEj; + public int numStagedRegular; + + private void setPackage(int userId, @NonNull String packageName) { + this.userId = userId; + this.packageName = packageName; + numRunningEj = numRunningRegular = 0; + resetStagedCount(); + } + + private void resetStagedCount() { + numStagedEj = numStagedRegular = 0; + } + + private void adjustRunningCount(boolean add, boolean forEj) { + if (forEj) { + numRunningEj = Math.max(0, numRunningEj + (add ? 1 : -1)); + } else { + numRunningRegular = Math.max(0, numRunningRegular + (add ? 1 : -1)); + } + } + + private void adjustStagedCount(boolean add, boolean forEj) { + if (forEj) { + numStagedEj = Math.max(0, numStagedEj + (add ? 1 : -1)); + } else { + numStagedRegular = Math.max(0, numStagedRegular + (add ? 1 : -1)); + } + } + + @GuardedBy("mLock") + private void dumpLocked(IndentingPrintWriter pw) { + pw.print("PackageStats{"); + pw.print(userId); + pw.print("-"); + pw.print(packageName); + pw.print("#runEJ", numRunningEj); + pw.print("#runReg", numRunningRegular); + pw.print("#stagedEJ", numStagedEj); + pw.print("#stagedReg", numStagedRegular); + pw.println("}"); + } + } } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index d72f565be4a0..181566169ed7 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -816,6 +816,7 @@ public class JobSchedulerService extends com.android.server.SystemService mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } mDebuggableApps.remove(pkgName); + mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid); } } else if (Intent.ACTION_USER_ADDED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); @@ -2233,7 +2234,8 @@ public class JobSchedulerService extends com.android.server.SystemService } /** Returns true if both the calling and source users for the job are started. */ - private boolean areUsersStartedLocked(final JobStatus job) { + @GuardedBy("mLock") + public boolean areUsersStartedLocked(final JobStatus job) { boolean sourceStarted = ArrayUtils.contains(mStartedUsers, job.getSourceUserId()); if (job.getUserId() == job.getSourceUserId()) { return sourceStarted; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java index 999c53fb7daf..12d9c7f52fb5 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java @@ -34,6 +34,7 @@ import android.util.Slog; import android.util.SparseArrayMap; import android.util.proto.ProtoOutputStream; +import com.android.internal.annotations.GuardedBy; import com.android.server.job.JobSchedulerService; import java.util.Objects; @@ -58,13 +59,28 @@ public class ComponentController extends StateController { return; } switch (action) { + case Intent.ACTION_PACKAGE_ADDED: + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + // Only do this for app updates since new installs won't have any jobs + // scheduled. + final Uri uri = intent.getData(); + final String pkg = uri != null ? uri.getSchemeSpecificPart() : null; + if (pkg != null) { + final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final int userId = UserHandle.getUserId(pkgUid); + updateComponentStateForPackage(userId, pkg); + } + } + break; case Intent.ACTION_PACKAGE_CHANGED: final Uri uri = intent.getData(); final String pkg = uri != null ? uri.getSchemeSpecificPart() : null; final String[] changedComponents = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); if (pkg != null && changedComponents != null && changedComponents.length > 0) { - updateComponentStateForPackage(pkg); + final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final int userId = UserHandle.getUserId(pkgUid); + updateComponentStateForPackage(userId, pkg); } break; case Intent.ACTION_USER_UNLOCKED: @@ -86,6 +102,7 @@ public class ComponentController extends StateController { super(service); final IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addDataScheme("package"); mContext.registerReceiverAsUser( @@ -99,6 +116,7 @@ public class ComponentController extends StateController { } @Override + @GuardedBy("mLock") public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { updateComponentEnabledStateLocked(jobStatus); } @@ -108,30 +126,53 @@ public class ComponentController extends StateController { boolean forUpdate) { } + @Override + @GuardedBy("mLock") + public void onAppRemovedLocked(String packageName, int uid) { + clearComponentsForPackageLocked(UserHandle.getUserId(uid), packageName); + } + + @Override + @GuardedBy("mLock") + public void onUserRemovedLocked(int userId) { + mServiceInfoCache.delete(userId); + } + @Nullable - private ServiceInfo getServiceInfo(JobStatus jobStatus) { + @GuardedBy("mLock") + private ServiceInfo getServiceInfoLocked(JobStatus jobStatus) { final ComponentName service = jobStatus.getServiceComponent(); final int userId = jobStatus.getUserId(); - ServiceInfo si = mServiceInfoCache.get(userId, service); - if (si == null) { - try { - // createContextAsUser may potentially be expensive - // TODO: cache user context or improve ContextImpl implementation if this becomes - // a problem - si = mContext.createContextAsUser(UserHandle.of(userId), 0) - .getPackageManager() - .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO); - } catch (NameNotFoundException e) { + if (mServiceInfoCache.contains(userId, service)) { + // Return whatever is in the cache, even if it's null. When something changes, we + // clear the cache. + return mServiceInfoCache.get(userId, service); + } + + ServiceInfo si; + try { + // createContextAsUser may potentially be expensive + // TODO: cache user context or improve ContextImpl implementation if this becomes + // a problem + si = mContext.createContextAsUser(UserHandle.of(userId), 0) + .getPackageManager() + .getServiceInfo(service, PackageManager.MATCH_DIRECT_BOOT_AUTO); + } catch (NameNotFoundException e) { + if (mService.areUsersStartedLocked(jobStatus)) { + // User is fully unlocked but PM still says the package doesn't exist. Slog.e(TAG, "Job exists for non-existent package: " + service.getPackageName()); - return null; } - mServiceInfoCache.add(userId, service, si); + // Write null to the cache so we don't keep querying PM. + si = null; } + mServiceInfoCache.add(userId, service, si); + return si; } + @GuardedBy("mLock") private boolean updateComponentEnabledStateLocked(JobStatus jobStatus) { - final ServiceInfo service = getServiceInfo(jobStatus); + final ServiceInfo service = getServiceInfoLocked(jobStatus); if (DEBUG && service == null) { Slog.v(TAG, jobStatus.toShortString() + " component not present"); @@ -141,20 +182,26 @@ public class ComponentController extends StateController { return !Objects.equals(ogService, service); } - private void updateComponentStateForPackage(final String pkg) { - synchronized (mLock) { - for (int u = mServiceInfoCache.numMaps() - 1; u >= 0; --u) { - final int userId = mServiceInfoCache.keyAt(u); - - for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) { - final ComponentName cn = mServiceInfoCache.keyAt(u, c); - if (cn.getPackageName().equals(pkg)) { - mServiceInfoCache.delete(userId, cn); - } - } + @GuardedBy("mLock") + private void clearComponentsForPackageLocked(final int userId, final String pkg) { + final int uIdx = mServiceInfoCache.indexOfKey(userId); + for (int c = mServiceInfoCache.numElementsForKey(userId) - 1; c >= 0; --c) { + final ComponentName cn = mServiceInfoCache.keyAt(uIdx, c); + if (cn.getPackageName().equals(pkg)) { + mServiceInfoCache.delete(userId, cn); } - updateComponentStatesLocked( - jobStatus -> jobStatus.getServiceComponent().getPackageName().equals(pkg)); + } + } + + private void updateComponentStateForPackage(final int userId, final String pkg) { + synchronized (mLock) { + clearComponentsForPackageLocked(userId, pkg); + updateComponentStatesLocked(jobStatus -> { + // Using user ID instead of source user ID because the service will run under the + // user ID, not source user ID. + return jobStatus.getUserId() == userId + && jobStatus.getServiceComponent().getPackageName().equals(pkg); + }); } } @@ -169,6 +216,7 @@ public class ComponentController extends StateController { } } + @GuardedBy("mLock") private void updateComponentStatesLocked(@NonNull Predicate<JobStatus> filter) { mComponentStateUpdateFunctor.reset(); mService.getJobStore().forEachJob(filter, mComponentStateUpdateFunctor); @@ -178,24 +226,40 @@ public class ComponentController extends StateController { } final class ComponentStateUpdateFunctor implements Consumer<JobStatus> { + @GuardedBy("mLock") boolean mChanged; @Override + @GuardedBy("mLock") public void accept(JobStatus jobStatus) { mChanged |= updateComponentEnabledStateLocked(jobStatus); } + @GuardedBy("mLock") private void reset() { mChanged = false; } } @Override + @GuardedBy("mLock") public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { - + for (int u = 0; u < mServiceInfoCache.numMaps(); ++u) { + final int userId = mServiceInfoCache.keyAt(u); + for (int p = 0; p < mServiceInfoCache.numElementsForKey(userId); ++p) { + final ComponentName componentName = mServiceInfoCache.keyAt(u, p); + pw.print(userId); + pw.print("-"); + pw.print(componentName); + pw.print(": "); + pw.print(mServiceInfoCache.valueAt(u, p)); + pw.println(); + } + } } @Override + @GuardedBy("mLock") public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 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/apex/media/framework/api/current.txt b/apex/media/framework/api/current.txt index bebf0190191b..b7d7ed866c89 100644 --- a/apex/media/framework/api/current.txt +++ b/apex/media/framework/api/current.txt @@ -10,7 +10,6 @@ package android.media { method @NonNull public java.util.List<java.lang.String> getUnsupportedVideoMimeTypes(); method public boolean isFormatSpecified(@NonNull String); method public boolean isHdrTypeSupported(@NonNull String); - method public boolean isSlowMotionSupported(); method public boolean isVideoMimeTypeSupported(@NonNull String); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.media.ApplicationMediaCapabilities> CREATOR; diff --git a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java index 3f30d3ea880e..97fa0eca0862 100644 --- a/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java +++ b/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java @@ -242,7 +242,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { } }; - /* + /** * Query the video codec mime types supported by the application. * @return List of supported video codec mime types. The list will be empty if there are none. */ @@ -251,7 +251,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mSupportedVideoMimeTypes); } - /* + /** * Query the video codec mime types that are not supported by the application. * @return List of unsupported video codec mime types. The list will be empty if there are none. */ @@ -260,7 +260,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mUnsupportedVideoMimeTypes); } - /* + /** * Query all hdr types that are supported by the application. * @return List of supported hdr types. The list will be empty if there are none. */ @@ -269,7 +269,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mSupportedHdrTypes); } - /* + /** * Query all hdr types that are not supported by the application. * @return List of unsupported hdr types. The list will be empty if there are none. */ @@ -278,7 +278,7 @@ public final class ApplicationMediaCapabilities implements Parcelable { return new ArrayList<>(mUnsupportedHdrTypes); } - /* + /** * Whether handling of slow-motion video is supported * @hide */ 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..5b978e5cdae2 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 { @@ -2319,7 +2321,7 @@ package android.content { field public static final String SYSTEM_CONFIG_SERVICE = "system_config"; field public static final String SYSTEM_UPDATE_SERVICE = "system_update"; field public static final String TETHERING_SERVICE = "tethering"; - field public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; + field public static final String TRANSLATION_MANAGER_SERVICE = "translation"; field public static final String UI_TRANSLATION_SERVICE = "ui_translation"; field public static final String VR_SERVICE = "vrmanager"; field public static final String WIFI_NL80211_SERVICE = "wifinl80211"; @@ -7296,10 +7298,6 @@ package android.metrics { package android.net { - public class DnsResolverServiceManager { - method @NonNull @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public static android.os.IBinder getService(@NonNull android.content.Context); - } - public class EthernetManager { method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public android.net.EthernetManager.TetheredInterfaceRequest requestTetheredInterface(@NonNull java.util.concurrent.Executor, @NonNull android.net.EthernetManager.TetheredInterfaceCallback); } @@ -7859,7 +7857,7 @@ package android.net.wifi.nl80211 { method @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.TxPacketCounters getTxPacketCounters(@NonNull String); method @Nullable public static android.net.wifi.nl80211.WifiNl80211Manager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]); method @Deprecated public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SoftApCallback); - method public boolean registerCountryCodeChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); + method public boolean registerCountryCodeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.SendMgmtFrameCallback); method public void setOnServiceDeadCallback(@NonNull Runnable); method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.ScanEventCallback); @@ -7872,7 +7870,7 @@ package android.net.wifi.nl80211 { method public boolean tearDownClientInterface(@NonNull String); method public boolean tearDownInterfaces(); method public boolean tearDownSoftApInterface(@NonNull String); - method public void unregisterCountryCodeChangeListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangeListener); + method public void unregisterCountryCodeChangedListener(@NonNull android.net.wifi.nl80211.WifiNl80211Manager.CountryCodeChangedListener); field public static final String SCANNING_PARAM_ENABLE_6GHZ_RNR = "android.net.wifi.nl80211.SCANNING_PARAM_ENABLE_6GHZ_RNR"; field public static final int SCAN_TYPE_PNO_SCAN = 1; // 0x1 field public static final int SCAN_TYPE_SINGLE_SCAN = 0; // 0x0 @@ -7883,8 +7881,8 @@ package android.net.wifi.nl80211 { field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1 } - public static interface WifiNl80211Manager.CountryCodeChangeListener { - method public void onChanged(@NonNull String); + public static interface WifiNl80211Manager.CountryCodeChangedListener { + method public void onCountryCodeChanged(@NonNull String); } public static class WifiNl80211Manager.OemSecurityType { @@ -7942,16 +7940,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 +8964,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 20e91873a7e0..e449728ca0a8 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -161,6 +161,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void moveTaskToRootTask(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksInWindowingModes(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void removeRootTasksWithActivityTypes(@NonNull int[]); + method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void resizeTask(int, android.graphics.Rect); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void startSystemLockTaskMode(int); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void stopSystemLockTaskMode(); @@ -1574,6 +1575,10 @@ package android.net { method public static void setServiceForTest(@Nullable android.os.IBinder); } + public class NetworkWatchlistManager { + method @Nullable public byte[] getWatchlistConfigHash(); + } + public class TrafficStats { method public static long getLoopbackRxBytes(); method public static long getLoopbackRxPackets(); @@ -2009,6 +2014,7 @@ package android.permission { public final class PermissionManager { method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(); + method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.permission.PermGroupUsage> getIndicatorAppOpUsageData(boolean); method @NonNull public android.content.AttributionSource registerAttributionSource(@NonNull android.content.AttributionSource); } @@ -3172,5 +3178,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/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 627017ce0893..28d6fbb36a47 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -469,7 +469,8 @@ public class ActivityTaskManager { } } - /** @hide */ + /** Removes task by a given taskId */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean removeTask(int taskId) { try { return getService().removeTask(taskId); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index df9530fee68a..dd5e7f35dedb 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -6996,6 +6996,20 @@ public class AppOpsManager { } result.add(entry); } + nAccesses = result.size(); + int i = 0; + for (int j = 0, k = 0; j < nAccesses; i++, j = k) { + long currentAccessTime = result.get(j).getLastAccessTime(OP_FLAGS_ALL); + k = j + 1; + while(k < nAccesses && + result.get(k).getLastAccessTime(OP_FLAGS_ALL) == currentAccessTime) { + k++; + } + result.set(i, mergeAttributedOpEntries(result.subList(j, k))); + } + for (; i < nAccesses; i++) { + result.remove(result.size() - 1); + } return result; } @@ -9819,4 +9833,35 @@ public class AppOpsManager { } } } + + private static AttributedOpEntry mergeAttributedOpEntries(List<AttributedOpEntry> opEntries) { + if (opEntries.size() == 1) { + return opEntries.get(0); + } + LongSparseArray<AppOpsManager.NoteOpEvent> accessEvents = new LongSparseArray<>(); + LongSparseArray<AppOpsManager.NoteOpEvent> rejectEvents = new LongSparseArray<>(); + int opCount = opEntries.size(); + for (int i = 0; i < opCount; i++) { + AttributedOpEntry a = opEntries.get(i); + ArraySet<Long> keys = a.collectKeys(); + final int keyCount = keys.size(); + for (int k = 0; k < keyCount; k++) { + final long key = keys.valueAt(k); + + final int uidState = extractUidStateFromKey(key); + final int flags = extractFlagsFromKey(key); + + NoteOpEvent access = a.getLastAccessEvent(uidState, uidState, flags); + NoteOpEvent reject = a.getLastRejectEvent(uidState, uidState, flags); + + if (access != null) { + accessEvents.append(key, access); + } + if (reject != null) { + rejectEvents.append(key, reject); + } + } + } + return new AttributedOpEntry(opEntries.get(0).mOp, false, accessEvents, rejectEvents); + } } diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java index dfc105a2811a..8574678a5a02 100644 --- a/core/java/android/app/ApplicationExitInfo.java +++ b/core/java/android/app/ApplicationExitInfo.java @@ -614,7 +614,7 @@ public final class ApplicationExitInfo implements Parcelable { * tombstone traces will be returned for * {@link #REASON_CRASH_NATIVE}, with an InputStream containing a protobuf with * <a href="https://android.googlesource.com/platform/system/core/+/refs/heads/master/debuggerd/proto/tombstone.proto">this schema</a>. - * Note thatbecause these traces are kept in a separate global circular buffer, crashes may be + * Note that because these traces are kept in a separate global circular buffer, crashes may be * overwritten by newer crashes (including from other applications), so this may still return * null. * diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 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..a88c9edd3017 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 @@ -4694,10 +4705,9 @@ public abstract class Context { * @hide * @see #getSystemService(String) */ - // TODO(b/176208267): change it back to translation before S release. @SystemApi @SuppressLint("ServiceName") - public static final String TRANSLATION_MANAGER_SERVICE = "transformer"; + public static final String TRANSLATION_MANAGER_SERVICE = "translation"; /** * Official published name of the translation service which supports ui translation function. 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/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java index 98a20f73a120..52ee4de5bed6 100644 --- a/core/java/android/content/pm/PackagePartitions.java +++ b/core/java/android/content/pm/PackagePartitions.java @@ -47,7 +47,7 @@ public class PackagePartitions { public static final int PARTITION_PRODUCT = 4; public static final int PARTITION_SYSTEM_EXT = 5; - @IntDef(flag = true, prefix = { "PARTITION_" }, value = { + @IntDef(prefix = { "PARTITION_" }, value = { PARTITION_SYSTEM, PARTITION_VENDOR, PARTITION_ODM, diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index a1ffc0ca5378..0fc6b2bd4f2e 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()) { @@ -2799,7 +2807,15 @@ public class ParsingPackageUtils { } } + @SuppressWarnings("AndroidFrameworkCompatChange") private void convertSplitPermissions(ParsingPackage pkg) { + // STOPSHIP(b/183905675): REMOVE THIS TERRIBLE, HORRIBLE, NO GOOD, VERY BAD HACK + if ("com.android.chrome".equals(pkg.getPackageName()) + && pkg.getVersionCode() <= 445500399 + && pkg.getTargetSdkVersion() > Build.VERSION_CODES.R) { + pkg.setTargetSdkVersion(Build.VERSION_CODES.R); + } + final int listSize = mSplitPermissionInfos.size(); for (int is = 0; is < listSize; is++) { final PermissionManager.SplitPermissionInfo spi = mSplitPermissionInfos.get(is); diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java index 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/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 4ee5383a56be..f03da7cd390b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -718,7 +718,7 @@ public class InputMethodService extends AbstractInputMethodService { public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken) { - mPrivOps.reportStartInput(startInputToken); + mPrivOps.reportStartInputAsync(startInputToken); if (restarting) { restartInput(inputConnection, editorInfo); diff --git a/core/java/android/net/DnsResolverServiceManager.java b/core/java/android/net/DnsResolverServiceManager.java deleted file mode 100644 index 15973224f10b..000000000000 --- a/core/java/android/net/DnsResolverServiceManager.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package android.net; - -import android.annotation.NonNull; -import android.annotation.RequiresPermission; -import android.annotation.SystemApi; -import android.content.Context; -import android.os.IBinder; -import android.os.ServiceManager; - -import java.util.Objects; - -/** - * Provides a way to obtain the DnsResolver binder objects. - * - * @hide - */ -@SystemApi -public class DnsResolverServiceManager { - /** - * Name to retrieve a {@link android.net.IDnsResolver} IBinder. - */ - private static final String DNS_RESOLVER_SERVICE = "dnsresolver"; - - private DnsResolverServiceManager() {} - - /** - * Get an {@link IBinder} representing the DnsResolver stable AIDL interface - * - * @param context the context for permission check. - * @return {@link android.net.IDnsResolver} IBinder. - */ - @NonNull - @RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) - public static IBinder getService(@NonNull final Context context) { - Objects.requireNonNull(context); - context.enforceCallingOrSelfPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - "DnsResolverServiceManager"); - try { - return ServiceManager.getServiceOrThrow(DNS_RESOLVER_SERVICE); - } catch (ServiceManager.ServiceNotFoundException e) { - // Catch ServiceManager#ServiceNotFoundException and rethrow IllegalStateException - // because ServiceManager#ServiceNotFoundException is @hide so that it can't be listed - // on the system api. Thus, rethrow IllegalStateException if dns resolver service cannot - // be found. - throw new IllegalStateException("Cannot find dns resolver service."); - } - } -} diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java index 8f6510ed3ea5..da01dcb83de4 100644 --- a/core/java/android/net/NetworkWatchlistManager.java +++ b/core/java/android/net/NetworkWatchlistManager.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.Nullable; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; @@ -31,6 +32,7 @@ import com.android.internal.util.Preconditions; * Class that manage network watchlist in system. * @hide */ +@TestApi @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SystemService(Context.NETWORK_WATCHLIST_SERVICE) public class NetworkWatchlistManager { 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/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 1a40f0640a8d..17c90d64ce6a 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -886,6 +886,24 @@ public final class PermissionManager { } /** + * @param micMuted whether to consider the microphone muted when retrieving audio ops + * @return A list of permission groups currently or recently used by all apps by all users in + * the current profile group. + * + * @hide + */ + @TestApi + @NonNull + @RequiresPermission(Manifest.permission.GET_APP_OPS_STATS) + public List<PermGroupUsage> getIndicatorAppOpUsageData(boolean micMuted) { + // Lazily initialize the usage helper + if (mUsageHelper == null) { + mUsageHelper = new PermissionUsageHelper(mContext); + } + return mUsageHelper.getOpUsageData(micMuted); + } + + /** * Determine if a package should be shown in indicators. Only a select few roles, and the * system app itself, are hidden. These values are updated at most every 15 seconds. * @hide diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 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/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index d89c29a5a360..87fb611d2a0b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -37,6 +37,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.TypedArray; +import android.graphics.BLASTBufferQueue; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; @@ -208,8 +209,8 @@ public abstract class WallpaperService extends Service { int mCurHeight; float mZoom = 0f; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; - int mWindowPrivateFlags = - WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; + int mWindowPrivateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS + | WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; Rect mPreviewSurfacePosition; @@ -253,6 +254,7 @@ public abstract class WallpaperService extends Service { private int mDisplayState; SurfaceControl mSurfaceControl = new SurfaceControl(); + BLASTBufferQueue mBlastBufferQueue; final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { { @@ -974,7 +976,14 @@ public abstract class WallpaperService extends Service { View.VISIBLE, 0, -1, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSurfaceSize); if (mSurfaceControl.isValid()) { - mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); + Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x, + mSurfaceSize.y, mFormat); + // If blastSurface == null that means it hasn't changed since the last + // time we called. In this situation, avoid calling transferFrom as we + // would then inc the generation ID and cause EGL resources to be recreated. + if (blastSurface != null) { + mSurfaceHolder.mSurface.transferFrom(blastSurface); + } } if (!mLastSurfaceSize.equals(mSurfaceSize)) { mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); @@ -1455,13 +1464,12 @@ public abstract class WallpaperService extends Service { return; } Surface surface = mSurfaceHolder.getSurface(); - boolean widthIsLarger = - mSurfaceControl.getWidth() > mSurfaceControl.getHeight(); - int smaller = widthIsLarger ? mSurfaceControl.getWidth() - : mSurfaceControl.getHeight(); + boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; + int smaller = widthIsLarger ? mSurfaceSize.x + : mSurfaceSize.y; float ratio = (float) MIN_BITMAP_SCREENSHOT_WIDTH / (float) smaller; - int width = (int) (ratio * mSurfaceControl.getWidth()); - int height = (int) (ratio * mSurfaceControl.getHeight()); + int width = (int) (ratio * mSurfaceSize.x); + int height = (int) (ratio * mSurfaceSize.y); if (width <= 0 || height <= 0) { Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height); return; @@ -1842,6 +1850,21 @@ public abstract class WallpaperService extends Service { public void onDisplayAdded(int displayId) { } }; + + private Surface getOrCreateBLASTSurface(int width, int height, int format) { + Surface ret = null; + if (mBlastBufferQueue == null) { + mBlastBufferQueue = new BLASTBufferQueue("Wallpaper", mSurfaceControl, width, + height, format); + // We only return the Surface the first time, as otherwise + // it hasn't changed and there is no need to update. + ret = mBlastBufferQueue.createSurface(); + } else { + mBlastBufferQueue.update(mSurfaceControl, width, height, format); + } + + return ret; + } } private boolean isValid(RectF area) { diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 340fa406bbb7..4b18c5aae614 100644 --- a/core/java/android/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -796,13 +796,14 @@ public class TelephonyRegistryManager { /** * Notify {@link PhysicalChannelConfig} has changed for a specific subscription. * + * @param slotIndex for which physical channel configs changed. * @param subId the subId * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel. */ - public void notifyPhysicalChannelConfigForSubscriber( - int subId, List<PhysicalChannelConfig> configs) { + public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId, + List<PhysicalChannelConfig> configs) { try { - sRegistry.notifyPhysicalChannelConfigForSubscriber(subId, configs); + sRegistry.notifyPhysicalChannelConfigForSubscriber(slotIndex, subId, configs); } catch (RemoteException ex) { // system server crash } diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 0a3963d782b1..be172f748b55 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -693,71 +693,79 @@ public final class Choreographer { ThreadedRenderer.setFPSDivisor(divisor); } + private void traceMessage(String msg) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, msg); + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } + void doFrame(long frameTimeNanos, int frame, DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; - synchronized (mLock) { - if (!mFrameScheduled) { - return; // no work to do - } - - if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { - mDebugPrintNextFrameTimeDelta = false; - Log.d(TAG, "Frame time delta: " - + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "Choreographer#doFrame " + vsyncEventData.id); } - - long intendedFrameTimeNanos = frameTimeNanos; - startNanos = System.nanoTime(); - final long jitterNanos = startNanos - frameTimeNanos; - if (jitterNanos >= frameIntervalNanos) { - final long skippedFrames = jitterNanos / frameIntervalNanos; - if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { - Log.i(TAG, "Skipped " + skippedFrames + " frames! " - + "The application may be doing too much work on its main thread."); + synchronized (mLock) { + if (!mFrameScheduled) { + traceMessage("Frame not scheduled"); + return; // no work to do } - final long lastFrameOffset = jitterNanos % frameIntervalNanos; - if (DEBUG_JANK) { - Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " - + "which is more than the frame interval of " - + (frameIntervalNanos * 0.000001f) + " ms! " - + "Skipping " + skippedFrames + " frames and setting frame " - + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); + + if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) { + mDebugPrintNextFrameTimeDelta = false; + Log.d(TAG, "Frame time delta: " + + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms"); } - frameTimeNanos = startNanos - lastFrameOffset; - } - if (frameTimeNanos < mLastFrameTimeNanos) { - if (DEBUG_JANK) { - Log.d(TAG, "Frame time appears to be going backwards. May be due to a " - + "previously skipped frame. Waiting for next vsync."); + long intendedFrameTimeNanos = frameTimeNanos; + startNanos = System.nanoTime(); + final long jitterNanos = startNanos - frameTimeNanos; + if (jitterNanos >= frameIntervalNanos) { + final long skippedFrames = jitterNanos / frameIntervalNanos; + if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { + Log.i(TAG, "Skipped " + skippedFrames + " frames! " + + "The application may be doing too much work on its main thread."); + } + final long lastFrameOffset = jitterNanos % frameIntervalNanos; + if (DEBUG_JANK) { + Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms " + + "which is more than the frame interval of " + + (frameIntervalNanos * 0.000001f) + " ms! " + + "Skipping " + skippedFrames + " frames and setting frame " + + "time to " + (lastFrameOffset * 0.000001f) + " ms in the past."); + } + frameTimeNanos = startNanos - lastFrameOffset; } - scheduleVsyncLocked(); - return; - } - if (mFPSDivisor > 1) { - long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; - if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + if (frameTimeNanos < mLastFrameTimeNanos) { + if (DEBUG_JANK) { + Log.d(TAG, "Frame time appears to be going backwards. May be due to a " + + "previously skipped frame. Waiting for next vsync."); + } + traceMessage("Frame time goes backward"); scheduleVsyncLocked(); return; } - } - mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, - vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); - mFrameScheduled = false; - mLastFrameTimeNanos = frameTimeNanos; - mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData = vsyncEventData; - } + if (mFPSDivisor > 1) { + long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos; + if (timeSinceVsync < (frameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) { + traceMessage("Frame skipped due to FPSDivisor"); + scheduleVsyncLocked(); + return; + } + } - try { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.id); + mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos, vsyncEventData.id, + vsyncEventData.frameDeadline, startNanos, vsyncEventData.frameInterval); + mFrameScheduled = false; + mLastFrameTimeNanos = frameTimeNanos; + mLastFrameIntervalNanos = frameIntervalNanos; + mLastVsyncEventData = vsyncEventData; } + AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); @@ -870,7 +878,12 @@ public final class Choreographer { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void scheduleVsyncLocked() { - mDisplayEventReceiver.scheduleVsync(); + try { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked"); + mDisplayEventReceiver.scheduleVsync(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } private boolean isRunningOnLooperThreadLocked() { @@ -967,32 +980,40 @@ public final class Choreographer { @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame, VsyncEventData vsyncEventData) { - // Post the vsync event to the Handler. - // The idea is to prevent incoming vsync events from completely starving - // the message queue. If there are no messages in the queue with timestamps - // earlier than the frame time, then the vsync event will be processed immediately. - // Otherwise, messages that predate the vsync event will be handled first. - long now = System.nanoTime(); - if (timestampNanos > now) { - Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f) - + " ms in the future! Check that graphics HAL is generating vsync " - + "timestamps using the correct timebase."); - timestampNanos = now; - } + try { + if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + Trace.traceBegin(Trace.TRACE_TAG_VIEW, + "Choreographer#onVsync " + vsyncEventData.id); + } + // Post the vsync event to the Handler. + // The idea is to prevent incoming vsync events from completely starving + // the message queue. If there are no messages in the queue with timestamps + // earlier than the frame time, then the vsync event will be processed immediately. + // Otherwise, messages that predate the vsync event will be handled first. + long now = System.nanoTime(); + if (timestampNanos > now) { + Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f) + + " ms in the future! Check that graphics HAL is generating vsync " + + "timestamps using the correct timebase."); + timestampNanos = now; + } - if (mHavePendingVsync) { - Log.w(TAG, "Already have a pending vsync event. There should only be " - + "one at a time."); - } else { - mHavePendingVsync = true; - } + if (mHavePendingVsync) { + Log.w(TAG, "Already have a pending vsync event. There should only be " + + "one at a time."); + } else { + mHavePendingVsync = true; + } - mTimestampNanos = timestampNanos; - mFrame = frame; - mLastVsyncEventData = vsyncEventData; - Message msg = Message.obtain(mHandler, this); - msg.setAsynchronous(true); - mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); + mTimestampNanos = timestampNanos; + mFrame = frame; + mLastVsyncEventData = vsyncEventData; + Message msg = Message.obtain(mHandler, this); + msg.setAsynchronous(true); + mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } @Override diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java index a6d786e1db21..5fcb011c76f1 100644 --- a/core/java/android/view/ScrollCaptureConnection.java +++ b/core/java/android/view/ScrollCaptureConnection.java @@ -185,7 +185,8 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub { } Log.w(TAG, "close(): capture session still active! Ending now."); // -> UiThread - mUiThread.execute(() -> mLocal.onScrollCaptureEnd(() -> { /* ignore */ })); + final ScrollCaptureCallback callback = mLocal; + mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ })); mActive = false; } mActive = false; diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 21f75d419a5e..2c81e8986ac6 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -35,6 +35,7 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.graphics.Bitmap; import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -188,6 +189,10 @@ public final class SurfaceControl implements Parcelable { IBinder displayToken, int mode); private static native void nativeReparent(long transactionObj, long nativeObject, long newParentNativeObject); + private static native void nativeSetBuffer(long transactionObj, long nativeObject, + GraphicBuffer buffer); + private static native void nativeSetColorSpace(long transactionObj, long nativeObject, + int colorSpace); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); @@ -3362,6 +3367,31 @@ public final class SurfaceControl implements Parcelable { return this; } + /** + * Set a buffer for a SurfaceControl. This can only be used for SurfaceControls that were + * created as type {@link #FX_SURFACE_BLAST} + * + * @hide + */ + public Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + checkPreconditions(sc); + nativeSetBuffer(mNativeObject, sc.mNativeObject, buffer); + return this; + } + + /** + * Set the color space for the SurfaceControl. The supported color spaces are SRGB + * and Display P3, other color spaces will be treated as SRGB. This can only be used for + * SurfaceControls that were created as type {@link #FX_SURFACE_BLAST} + * + * @hide + */ + public Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { + checkPreconditions(sc); + nativeSetColorSpace(mNativeObject, sc.mNativeObject, colorSpace.getId()); + return this; + } + /** * Merge the other transaction into this transaction, clearing the * other transaction as if it had been applied. 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/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 616910ab09ca..d6292caba344 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -581,7 +581,7 @@ public final class InputMethodManager { */ public void reportPerceptible(IBinder windowToken, boolean perceptible) { try { - mService.reportPerceptible(windowToken, perceptible); + mService.reportPerceptibleAsync(windowToken, perceptible); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } 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/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 20e520efc761..436596611f83 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -30,7 +30,7 @@ import com.android.internal.inputmethod.IVoidResultCallback; */ oneway interface IInputMethodPrivilegedOperations { void setImeWindowStatusAsync(int vis, int backDisposition); - void reportStartInput(in IBinder startInputToken, in IVoidResultCallback resultCallback); + void reportStartInputAsync(in IBinder startInputToken); void createInputContentUriToken(in Uri contentUri, in String packageName, in IIInputContentUriTokenResultCallback resultCallback); void reportFullscreenMode(boolean fullscreen, in IVoidResultCallback resultCallback); diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 10009140a7c0..555488d68866 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -123,21 +123,18 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#reportStartInput(IBinder, - * IVoidResultCallback)}. + * Calls {@link IInputMethodPrivilegedOperations#reportStartInputAsync(IBinder)}. * * @param startInputToken {@link IBinder} token to distinguish startInput session */ @AnyThread - public void reportStartInput(IBinder startInputToken) { + public void reportStartInputAsync(IBinder startInputToken) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; } try { - final Completable.Void value = Completable.createVoid(); - ops.reportStartInput(startInputToken, ResultCallbacks.of(value)); - Completable.getResult(value); + ops.reportStartInputAsync(startInputToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index e438d39e42f6..a0a0f3276b99 100644 --- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl @@ -92,7 +92,7 @@ interface ITelephonyRegistry { void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity, String chosenPlmn, int domain, int causeCode, int additionalCauseCode); void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo); - void notifyPhysicalChannelConfigForSubscriber(in int subId, + void notifyPhysicalChannelConfigForSubscriber(in int phoneId, in int subId, in List<PhysicalChannelConfig> configs); void notifyDataEnabled(in int phoneId, int subId, boolean enabled, int reason); void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType); diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java index 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/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index fd13c26b05b2..93cd4e9046c6 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -85,7 +85,7 @@ interface IInputMethodManager { oneway void reportActivityView(in IInputMethodClient parentClient, int childDisplayId, in float[] matrixValues, in IVoidResultCallback resultCallback); - oneway void reportPerceptible(in IBinder windowToken, boolean perceptible); + oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible); /** Remove the IME surface. Requires INTERNAL_SYSTEM_WINDOW permission. */ oneway void removeImeSurface(in IVoidResultCallback resultCallback); /** Remove the IME surface. Requires passing the currently focused window. */ diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java index a41511b74a7d..8aa2d57e8ea6 100644 --- a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java +++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java @@ -17,6 +17,7 @@ package com.android.internal.view; import android.annotation.UiThread; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.HardwareRenderer; @@ -26,6 +27,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.RenderNode; import android.os.CancellationSignal; +import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.view.Display.ColorMode; @@ -53,11 +55,14 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa private static final String TAG = "ScrollCaptureViewSupport"; - private static final boolean WAIT_FOR_ANIMATION = true; + private static final String SETTING_CAPTURE_DELAY = "screenshot.scroll_capture_delay"; + private static final long SETTING_CAPTURE_DELAY_DEFAULT = 60L; // millis private final WeakReference<V> mWeakView; private final ScrollCaptureViewHelper<V> mViewHelper; private final ViewRenderer mRenderer; + private final long mPostScrollDelayMillis; + private boolean mStarted; private boolean mEnded; @@ -66,6 +71,10 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa mRenderer = new ViewRenderer(); // TODO(b/177649144): provide access to color space from android.media.Image mViewHelper = viewHelper; + Context context = containingView.getContext(); + ContentResolver contentResolver = context.getContentResolver(); + mPostScrollDelayMillis = Settings.Global.getLong(contentResolver, + SETTING_CAPTURE_DELAY, SETTING_CAPTURE_DELAY_DEFAULT); } /** Based on ViewRootImpl#updateColorModeIfNeeded */ @@ -120,37 +129,41 @@ public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCa public final void onScrollCaptureImageRequest(ScrollCaptureSession session, CancellationSignal signal, Rect requestRect, Consumer<Rect> onComplete) { if (signal.isCanceled()) { + Log.w(TAG, "onScrollCaptureImageRequest: cancelled!"); return; } + V view = mWeakView.get(); if (view == null || !view.isVisibleToUser()) { // Signal to the controller that we have a problem and can't continue. onComplete.accept(new Rect()); return; } + // Ask the view to scroll as needed to bring this area into view. ScrollResult scrollResult = mViewHelper.onScrollRequested(view, session.getScrollBounds(), requestRect); + if (scrollResult.availableArea.isEmpty()) { onComplete.accept(scrollResult.availableArea); return; } - view.invalidate(); // don't wait for vsync // For image capture, shift back by scrollDelta to arrive at the location within the view // where the requested content will be drawn Rect viewCaptureArea = new Rect(scrollResult.availableArea); viewCaptureArea.offset(0, -scrollResult.scrollDelta); - if (WAIT_FOR_ANIMATION) { - view.postOnAnimation(() -> { + Runnable captureAction = () -> { + if (signal.isCanceled()) { + Log.w(TAG, "onScrollCaptureImageRequest: cancelled! skipping render."); + } else { mRenderer.renderView(view, viewCaptureArea); onComplete.accept(new Rect(scrollResult.availableArea)); - }); - } else { - mRenderer.renderView(view, viewCaptureArea); - onComplete.accept(new Rect(scrollResult.availableArea)); - } + } + }; + + view.postOnAnimationDelayed(captureAction, mPostScrollDelayMillis); } @Override diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index ffba628f73ab..4194acbfe015 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -30,6 +30,7 @@ #include <android/hardware/display/IDeviceProductInfoConstants.h> #include <android/os/IInputConstants.h> #include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_graphics_GraphicBuffer.h> #include <android_runtime/android_hardware_HardwareBuffer.h> #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_view_SurfaceSession.h> @@ -253,6 +254,15 @@ constexpr jint fromDataspaceToNamedColorSpaceValue(const ui::Dataspace dataspace } } +constexpr ui::Dataspace fromNamedColorSpaceValueToDataspace(const jint colorSpace) { + switch (colorSpace) { + case JNamedColorSpace::DISPLAY_P3: + return ui::Dataspace::DISPLAY_P3; + default: + return ui::Dataspace::V0_SRGB; + } +} + constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) { switch (colorMode) { case ui::ColorMode::DISPLAY_P3: @@ -553,6 +563,23 @@ static void nativeSetGeometry(JNIEnv* env, jclass clazz, jlong transactionObj, j transaction->setGeometry(ctrl, source, dst, orientation); } +static void nativeSetBuffer(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jobject bufferObject) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + sp<GraphicBuffer> buffer( + android_graphics_GraphicBuffer_getNativeGraphicsBuffer(env, bufferObject)); + transaction->setBuffer(ctrl, buffer); +} + +static void nativeSetColorSpace(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, + jint colorSpace) { + auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); + SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject); + ui::Dataspace dataspace = fromNamedColorSpaceValueToDataspace(colorSpace); + transaction->setDataspace(ctrl, dataspace); +} + static void nativeSetBlurRegions(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobjectArray regions, jint regionsLength) { auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj); @@ -1877,6 +1904,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = { (void*)nativeGetDisplayedContentSample }, {"nativeSetGeometry", "(JJLandroid/graphics/Rect;Landroid/graphics/Rect;J)V", (void*)nativeSetGeometry }, + {"nativeSetBuffer", "(JJLandroid/graphics/GraphicBuffer;)V", + (void*)nativeSetBuffer }, + {"nativeSetColorSpace", "(JJI)V", + (void*)nativeSetColorSpace }, {"nativeSyncInputWindows", "(J)V", (void*)nativeSyncInputWindows }, {"nativeGetDisplayBrightnessSupport", "(Landroid/os/IBinder;)Z", diff --git a/core/proto/android/internal/OWNERS b/core/proto/android/internal/OWNERS new file mode 100644 index 000000000000..24e24c20a9cd --- /dev/null +++ b/core/proto/android/internal/OWNERS @@ -0,0 +1,2 @@ +# Binder +per-file binder_latency.proto = file:/core/java/com/android/internal/os/BINDER_OWNERS
\ No newline at end of file diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index a7127ad79401..b157146e70a3 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -308,6 +308,7 @@ message TaskProto { optional float minimize_amount = 27; optional bool created_by_organizer = 28; optional string affinity = 29; + optional bool has_child_pip_activity = 30; } /* represents ActivityRecordProto */ diff --git a/core/res/res/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/colors.xml b/core/res/res/values/colors.xml index 0213c60e9f60..3cc83c1362b0 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -427,4 +427,7 @@ <!-- Darkest shade of the secondary neutral color used by the system. Black. This value can be overlaid at runtime by OverlayManager RROs. --> <color name="system_neutral2_1000">#000000</color> + + <!-- Exposes colorSurface in cases where it can't be directly referenced. --> + <color name="surface_above_background">?attr/colorSurface</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 906a7403298f..f24d663ced16 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1772,6 +1772,9 @@ <!-- Add algorithm here --> </string-array> + <!-- Boolean indicating if placing the phone face down will result in a screen off. --> + <bool name="config_flipToScreenOffEnabled">true</bool> + <!-- Boolean indicating if current platform supports bluetooth SCO for off call use cases --> <bool name="config_bluetooth_sco_off_call">true</bool> @@ -4697,6 +4700,11 @@ --> <color name="config_letterboxBackgroundColor">#000</color> + <!-- Horizonal position of a center of the letterboxed app window. + 0 corresponds to the left side of the screen and 1 to the right side. If given value < 0 + or > 1, it is ignored and central positionis used (0.5). --> + <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item> + <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 34dcaa0c78b2..ec94b1896c87 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -275,6 +275,7 @@ <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" /> <java-symbol type="bool" name="config_avoidGfxAccel" /> <java-symbol type="bool" name="config_bluetooth_address_validation" /> + <java-symbol type="bool" name="config_flipToScreenOffEnabled" /> <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" /> <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" /> @@ -4185,6 +4186,7 @@ <java-symbol type="dimen" name="config_letterboxBackgroundWallaperDarkScrimAlpha" /> <java-symbol type="integer" name="config_letterboxBackgroundType" /> <java-symbol type="color" name="config_letterboxBackgroundColor" /> + <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> @@ -4341,4 +4343,5 @@ <java-symbol type="string" name="view_and_control_notification_content" /> <java-symbol type="layout" name="notification_expand_button"/> + <java-symbol type="color" name="surface_above_background" /> </resources> diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml new file mode 100644 index 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/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java index 22c71b527d48..e7b88c8df393 100644 --- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java +++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java @@ -32,6 +32,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.ICancellationSignal; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.filters.SmallTest; @@ -189,4 +190,15 @@ public class ScrollCaptureConnectionTest { verifyNoMoreInteractions(mRemote); } + @Test + public void testClose_whileActive() throws RemoteException { + mConnection.startCapture(mSurface, mRemote); + + mCallback.completeStartRequest(); + assertTrue(mConnection.isActive()); + + mConnection.close(); + mCallback.completeEndRequest(); + assertFalse(mConnection.isActive()); + } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java index c393d68c3f94..8225afc5f510 100644 --- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java @@ -244,7 +244,8 @@ public class TextClassificationTest { PendingIntent.getActivity( context, 0, new Intent("action1"), FLAG_IMMUTABLE))) .addAction(new RemoteAction(icon1, "title2", "desc2", - PendingIntent.getActivity(context, 0, new Intent("action2"), 0))) + PendingIntent.getActivity(context, 0, new Intent("action2"), + FLAG_IMMUTABLE))) .setEntityType(TextClassifier.TYPE_EMAIL, 0.5f) .setEntityType(TextClassifier.TYPE_PHONE, 0.4f) .build(); 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/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index 6698a01dc159..a138fee32550 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -40,7 +40,7 @@ <integer name="long_press_dock_anim_duration">250</integer> <!-- Animation duration for translating of one handed when trigger / dismiss. --> - <integer name="config_one_handed_translate_animation_duration">300</integer> + <integer name="config_one_handed_translate_animation_duration">800</integer> <!-- One handed mode default offset % of display size --> <fraction name="config_one_handed_offset">40%</fraction> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 11c146457844..dca598518432 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -739,14 +739,11 @@ public class BubbleController { return (isSummary && isSuppressedSummary) || isSuppressedBubble; } - private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, - Executor callbackExecutor) { + private void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback) { if (mBubbleData.isSummarySuppressed(groupKey)) { mBubbleData.removeSuppressedSummary(groupKey); if (callback != null) { - callbackExecutor.execute(() -> { - callback.accept(mBubbleData.getSummaryKey(groupKey)); - }); + callback.accept(mBubbleData.getSummaryKey(groupKey)); } } } @@ -1298,8 +1295,10 @@ public class BubbleController { public void removeSuppressedSummaryIfNecessary(String groupKey, Consumer<String> callback, Executor callbackExecutor) { mMainExecutor.execute(() -> { - BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, callback, - callbackExecutor); + Consumer<String> cb = callback != null + ? (key) -> callbackExecutor.execute(() -> callback.accept(key)) + : null; + BubbleController.this.removeSuppressedSummaryIfNecessary(groupKey, cb); }); } @@ -1340,10 +1339,13 @@ public class BubbleController { @Override public boolean handleDismissalInterception(BubbleEntry entry, - @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { + @Nullable List<BubbleEntry> children, IntConsumer removeCallback, + Executor callbackExecutor) { + IntConsumer cb = removeCallback != null + ? (index) -> callbackExecutor.execute(() -> removeCallback.accept(index)) + : null; return mMainExecutor.executeBlockingForResult(() -> { - return BubbleController.this.handleDismissalInterception(entry, children, - removeCallback); + return BubbleController.this.handleDismissalInterception(entry, children, cb); }, Boolean.class); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 9fc8aef47064..1bfb61929297 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -140,7 +140,7 @@ public interface Bubbles { * @return true if we want to intercept the dismissal of the entry, else false. */ boolean handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, - IntConsumer removeCallback); + IntConsumer removeCallback, Executor callbackExecutor); /** Set the proxy to commnuicate with SysUi side components. */ void setSysuiProxy(SysuiProxy proxy); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java index 125e322974bf..25dd3ca57b92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java @@ -22,8 +22,7 @@ import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.view.SurfaceControl; -import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; +import android.view.animation.BaseInterpolator; import android.window.WindowContainerToken; import androidx.annotation.VisibleForTesting; @@ -54,7 +53,7 @@ public class OneHandedAnimationController { public @interface TransitionDirection { } - private final Interpolator mOvershootInterpolator; + private final OneHandedInterpolator mInterpolator; private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap = new HashMap<>(); @@ -64,7 +63,7 @@ public class OneHandedAnimationController { */ public OneHandedAnimationController(Context context) { mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context); - mOvershootInterpolator = new OvershootInterpolator(); + mInterpolator = new OneHandedInterpolator(); } @SuppressWarnings("unchecked") @@ -102,7 +101,7 @@ public class OneHandedAnimationController { OneHandedTransitionAnimator setupOneHandedTransitionAnimator( OneHandedTransitionAnimator animator) { animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); - animator.setInterpolator(mOvershootInterpolator); + animator.setInterpolator(mInterpolator); animator.setFloatValues(FRACTION_START, FRACTION_END); return animator; } @@ -112,6 +111,8 @@ public class OneHandedAnimationController { * * @param <T> Type of property to animate, either offset (float) */ + // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator + // to implement One-Handed transition animation. (b/185129031) public abstract static class OneHandedTransitionAnimator<T> extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { @@ -297,4 +298,15 @@ public class OneHandedAnimationController { }; } } + + /** + * An Interpolator for One-Handed transition animation. + */ + public class OneHandedInterpolator extends BaseInterpolator { + @Override + public float getInterpolation(float input) { + return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f) + * (2.0f * Math.PI) / 4.0f) + 1); + } + } } 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/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp index 71f533c3fc4f..ab00dd5a487c 100644 --- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp +++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp @@ -24,6 +24,7 @@ #include "SkClipStack.h" #include "SkRect.h" #include "SkM44.h" +#include "utils/GLUtils.h" namespace android { namespace uirenderer { @@ -170,6 +171,8 @@ void GLFunctorDrawable::onDraw(SkCanvas* canvas) { setScissor(info.height, clipRegion.getBounds()); } + // WebView may swallow GL errors, so catch them here + GL_CHECKPOINT(LOW); mWebViewHandle->drawGl(info); if (clearStencilAfterFunctor) { diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp index 82814def6962..8ab9d2217a51 100644 --- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp +++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp @@ -60,12 +60,11 @@ void SkiaRecordingCanvas::punchHole(const SkRRect& rect) { // Add the marker annotation to allow HWUI to determine where the current // clip/transformation should be applied SkVector vector = rect.getSimpleRadii(); - const int dataSize = 2; - float data[dataSize]; + float data[2]; data[0] = vector.x(); data[1] = vector.y(); mRecorder.drawAnnotation(rect.rect(), HOLE_PUNCH_ANNOTATION.c_str(), - SkData::MakeWithCopy(data, dataSize)); + SkData::MakeWithCopy(data, 2 * sizeof(float))); // Clear the current rect within the layer itself SkPaint paint = SkPaint(); diff --git a/libs/hwui/pipeline/skia/TransformCanvas.cpp b/libs/hwui/pipeline/skia/TransformCanvas.cpp index 6bfbb0d270b7..a6e4c4cf9ca7 100644 --- a/libs/hwui/pipeline/skia/TransformCanvas.cpp +++ b/libs/hwui/pipeline/skia/TransformCanvas.cpp @@ -22,7 +22,7 @@ using namespace android::uirenderer::skiapipeline; void TransformCanvas::onDrawAnnotation(const SkRect& rect, const char* key, SkData* value) { if (HOLE_PUNCH_ANNOTATION == key) { - auto* rectParams = static_cast<const float*>(value->data()); + auto* rectParams = reinterpret_cast<const float*>(value->data()); float radiusX = rectParams[0]; float radiusY = rectParams[1]; SkRRect roundRect = SkRRect::MakeRectXY(rect, radiusX, radiusY); 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/Android.bp b/packages/Connectivity/framework/Android.bp index c62402d7d4c9..bb93af90a9be 100644 --- a/packages/Connectivity/framework/Android.bp +++ b/packages/Connectivity/framework/Android.bp @@ -100,6 +100,7 @@ java_sdk_library { "//frameworks/base", // Tests using hidden APIs + "//cts/tests/netlegacy22.api", "//external/sl4a:__subpackages__", "//frameworks/base/tests/net:__subpackages__", "//frameworks/libs/net/common/testutils", 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/ConnectivityFrameworkInitializer.java b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java index 92a792b78410..a2e218dcbb4b 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityFrameworkInitializer.java @@ -68,5 +68,11 @@ public final class ConnectivityFrameworkInitializer { return cm.startOrGetTestNetworkManager(); } ); + + SystemServiceRegistry.registerContextAwareService( + DnsResolverServiceManager.DNS_RESOLVER_SERVICE, + DnsResolverServiceManager.class, + (context, serviceBinder) -> new DnsResolverServiceManager(serviceBinder) + ); } } 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/DnsResolverServiceManager.java b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java new file mode 100644 index 000000000000..79009e8d629e --- /dev/null +++ b/packages/Connectivity/framework/src/android/net/DnsResolverServiceManager.java @@ -0,0 +1,45 @@ +/* + * 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.net; + +import android.annotation.NonNull; +import android.os.IBinder; + +/** + * Provides a way to obtain the DnsResolver binder objects. + * + * @hide + */ +public class DnsResolverServiceManager { + /** Service name for the DNS resolver. Keep in sync with DnsResolverService.h */ + public static final String DNS_RESOLVER_SERVICE = "dnsresolver"; + + private final IBinder mResolver; + + DnsResolverServiceManager(IBinder resolver) { + mResolver = resolver; + } + + /** + * Get an {@link IBinder} representing the DnsResolver stable AIDL interface + * + * @return {@link android.net.IDnsResolver} IBinder. + */ + @NonNull + public IBinder getService() { + return mResolver; + } +} 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/RestrictedLockUtils/res/values-gu/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml index a24456ea02cf..f57061a30c14 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values-gu/strings.xml @@ -18,5 +18,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="enabled_by_admin" msgid="6630472777476410137">"વ્યવસ્થાપકે ચાલુ કરેલ"</string> - <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલ"</string> + <string name="disabled_by_admin" msgid="4023569940620832713">"વ્યવસ્થાપકે બંધ કરેલું"</string> </resources> diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml index 199a2d6c7079..bddf43ce6917 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values-it/strings.xml @@ -18,5 +18,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="enabled_by_admin" msgid="6630472777476410137">"Attivata dall\'amministratore"</string> - <string name="disabled_by_admin" msgid="4023569940620832713">"Disattivata dall\'amministratore"</string> + <string name="disabled_by_admin" msgid="4023569940620832713">"Opzione disattivata dall\'amministratore"</string> </resources> 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/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 01ec447e5685..3da45210e8c2 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -9,6 +9,7 @@ import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable import android.view.GhostView import android.view.View @@ -186,6 +187,10 @@ open class GhostedViewLaunchAnimatorController( return drawable } + if (drawable is InsetDrawable) { + return drawable.drawable?.let { findGradientDrawable(it) } + } + if (drawable is LayerDrawable) { for (i in 0 until drawable.numberOfLayers) { val maybeGradient = drawable.getDrawable(i) @@ -255,6 +260,11 @@ open class GhostedViewLaunchAnimatorController( } private fun setXfermode(background: Drawable, mode: PorterDuffXfermode?) { + if (background is InsetDrawable) { + background.drawable?.let { setXfermode(it, mode) } + return + } + if (background !is LayerDrawable) { background.setXfermode(mode) return @@ -323,6 +333,11 @@ open class GhostedViewLaunchAnimatorController( return } + if (drawable is InsetDrawable) { + drawable.drawable?.let { applyBackgroundRadii(it, radii) } + return + } + if (drawable !is LayerDrawable) { return } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 00bea8da2efc..47a373ebf429 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -60,6 +60,8 @@ public interface ActivityStarter { */ void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade, int flags); void startActivity(Intent intent, boolean dismissShade); + void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController); void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void postStartActivityDismissingKeyguard(Intent intent, int delay); 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-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index 0fef9f15c373..7ac3020ae886 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -17,7 +17,7 @@ */ --> -<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> +<resources> <!-- Keyguard PIN pad styles --> <style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView"> <item name="android:textSize">@dimen/kg_status_line_font_size</item> @@ -32,7 +32,7 @@ <item name="android:stateListAnimator">@null</item> </style> <style name="NumPadKey" parent="Theme.SystemUI"> - <item name="android:colorControlNormal">?androidprv:attr/colorSurface</item> + <item name="android:colorControlNormal">@*android:color/surface_above_background</item> <item name="android:colorControlHighlight">?android:attr/colorAccent</item> <item name="android:background">@drawable/num_pad_key_background</item> </style> diff --git a/packages/SystemUI/res/drawable/ic_brightness.xml b/packages/SystemUI/res/drawable/ic_brightness.xml index f44333236a12..842af26ddfd3 100644 --- a/packages/SystemUI/res/drawable/ic_brightness.xml +++ b/packages/SystemUI/res/drawable/ic_brightness.xml @@ -1,29 +1,23 @@ <!-- -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. + ~ 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. + --> - 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. ---> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24.0" - android:viewportHeight="24.0"> - - <path - android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z" - /> - - <path - android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z" - android:fillColor="#FFFFFF" /> -</vector> +<level-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Levels in drawables go from 0 to 10000 --> + <!-- "One third" of the range per icon --> + <item android:maxLevel="3333" android:drawable="@drawable/ic_brightness_low" /> + <item android:maxLevel="6666" android:drawable="@drawable/ic_brightness_medium" /> + <item android:maxLevel="10000" android:drawable="@drawable/ic_brightness_full" /> +</level-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml b/packages/SystemUI/res/drawable/ic_brightness_full.xml index d72198874b73..f44333236a12 100644 --- a/packages/SystemUI/res/drawable/ic_brightness_thumb.xml +++ b/packages/SystemUI/res/drawable/ic_brightness_full.xml @@ -1,5 +1,5 @@ <!-- -Copyright (C) 2017 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. @@ -21,9 +21,9 @@ Copyright (C) 2017 The Android Open Source Project <path android:pathData="M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z" - android:fillColor="?android:attr/colorBackgroundFloating" /> + /> <path android:pathData=" M20,8.69 V4h-4.69L12,0.69L8.69,4H4v4.69L0.69,12L4,15.31V20h4.69L12,23.31L15.31,20H20v-4.69L23.31,12L20,8.69z M18,14.48V18h-3.52L12,20.48L9.52,18H6v-3.52L3.52,12L6,9.52V6h3.52L12,3.52L14.48,6H18v3.52L20.48,12L18,14.48z M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S14.76,7 12,7z" - android:fillColor="?android:attr/colorControlActivated" /> + android:fillColor="#FFFFFF" /> </vector> diff --git a/packages/SystemUI/res/drawable/ic_brightness_low.xml b/packages/SystemUI/res/drawable/ic_brightness_low.xml new file mode 100644 index 000000000000..b463556e20d0 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_brightness_low.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="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,9c1.65,0 3,1.35 3,3s-1.35,3 -3,3 -3,-1.35 -3,-3 1.35,-3 3,-3m0,-2c-2.76,0 -5,2.24 -5,5s2.24,5 5,5 5,-2.24 5,-5 -2.24,-5 -5,-5z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_brightness_medium.xml b/packages/SystemUI/res/drawable/ic_brightness_medium.xml new file mode 100644 index 000000000000..80acc4d565fa --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_brightness_medium.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="M20,8.69L20,4h-4.69L12,0.69 8.69,4L4,4v4.69L0.69,12 4,15.31L4,20h4.69L12,23.31 15.31,20L20,20v-4.69L23.31,12 20,8.69zM18,14.48L18,18h-3.52L12,20.48 9.52,18L6,18v-3.52L3.52,12 6,9.52L6,6h3.52L12,3.52 14.48,6L18,6v3.52L20.48,12 18,14.48zM12,17c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5v10z"/> +</vector> 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/drawable/qs_footer_action_chip_background.xml b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml index 6022206c208c..77b98711a304 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_chip_background.xml @@ -23,13 +23,18 @@ <item android:id="@android:id/mask"> <shape android:shape="rectangle"> <solid android:color="@android:color/white"/> + <corners android:radius="@dimen/screenshot_button_corner_radius"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="?attr/underSurfaceColor"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> <item> <shape android:shape="rectangle"> - <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/> - <solid android:color="@android:color/transparent"/> + <stroke android:width="1dp" android:color="@color/qs_footer_action_border"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml index c83077371bb0..4e9d380af319 100644 --- a/packages/SystemUI/res/layout/qs_paged_page_side_labels.xml +++ b/packages/SystemUI/res/drawable/system_animation_ongoing_dot.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2020 The Android Open Source Project +<!-- Copyright (C) 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. @@ -14,10 +13,14 @@ 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" /> + +<!-- dot drawable for ongoing system privacy events --> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid + android:color="@color/privacy_circle"/> + <size + android:width="6dp" + android:height="6dp" + /> +</shape>
\ No newline at end of file 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/people_tile_medium_empty.xml b/packages/SystemUI/res/layout/people_tile_medium_empty.xml index 7d9cbb907506..4236493dce91 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_empty.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_empty.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:layout_width="match_parent" android:layout_height="match_parent" @@ -22,57 +22,49 @@ <LinearLayout android:background="@drawable/people_space_tile_view_card" android:id="@+id/item" - android:orientation="vertical" + android:gravity="center" + android:paddingHorizontal="16dp" + android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> - + <ImageView + android:id="@+id/person_icon" + android:layout_marginTop="-2dp" + android:layout_marginStart="-2dp" + android:layout_width="64dp" + android:layout_height="64dp" /> + <ImageView + android:id="@+id/availability" + android:layout_marginStart="-2dp" + android:layout_width="10dp" + android:layout_height="10dp" + android:background="@drawable/circle_green_10dp" /> <LinearLayout - android:orientation="horizontal" - android:gravity="center" - android:layout_gravity="center" - android:paddingVertical="2dp" - android:paddingHorizontal="8dp" + android:orientation="vertical" + android:paddingStart="6dp" + android:gravity="top" android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/person_icon" - android:layout_width="64dp" - android:layout_height="64dp"/> - <ImageView - android:id="@+id/availability" - android:layout_marginStart="-2dp" - android:layout_width="10dp" - android:layout_height="10dp" - android:background="@drawable/circle_green_10dp"/> - <LinearLayout - android:orientation="vertical" - android:paddingStart="6dp" - android:gravity="top" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <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> + android:layout_height="wrap_content"> + <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> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml index c9e4945a7be2..70706600e6da 100644 --- a/packages/SystemUI/res/layout/people_tile_medium_with_content.xml +++ b/packages/SystemUI/res/layout/people_tile_medium_with_content.xml @@ -97,7 +97,7 @@ android:gravity="bottom" android:layout_gravity="center_vertical" android:orientation="horizontal" - android:paddingTop="4dp" + android:paddingTop="2dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:clipToOutline="true"> diff --git a/packages/SystemUI/res/layout/people_tile_small.xml b/packages/SystemUI/res/layout/people_tile_small.xml index 34aa8e498231..7c28fc10c184 100644 --- a/packages/SystemUI/res/layout/people_tile_small.xml +++ b/packages/SystemUI/res/layout/people_tile_small.xml @@ -21,7 +21,7 @@ <LinearLayout android:id="@+id/item" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="match_parent" android:layout_gravity="center" android:background="@drawable/people_space_tile_view_card" android:orientation="vertical" @@ -42,12 +42,12 @@ android:tint="?android:attr/colorAccent" android:layout_gravity="center" android:layout_width="18dp" - android:layout_height="22dp" - android:layout_weight="1" /> + android:layout_height="22dp" /> <TextView android:id="@+id/messages_count" android:layout_gravity="center" + android:gravity="center" android:paddingStart="8dp" android:paddingEnd="8dp" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" @@ -59,7 +59,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" - android:layout_weight="1" /> <TextView @@ -67,7 +66,6 @@ android:layout_gravity="center" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_weight="1" android:ellipsize="end" android:maxLines="1" android:paddingHorizontal="4dp" 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/rounded_corners.xml b/packages/SystemUI/res/layout/rounded_corners.xml index db892d78c556..04fe9184cf60 100644 --- a/packages/SystemUI/res/layout/rounded_corners.xml +++ b/packages/SystemUI/res/layout/rounded_corners.xml @@ -14,6 +14,8 @@ ** See the License for the specific language governing permissions and ** limitations under the License. --> + +<!-- TODO: remove this in favor of requiring top and bottom layouts --> <com.android.systemui.RegionInterceptingFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rounded_corners_default" @@ -26,6 +28,25 @@ android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="8dp" + android:layout_gravity="left|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot_left" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +54,22 @@ android:tint="#ff000000" android:layout_gravity="right|bottom" android:src="@drawable/rounded"/> + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="8dp" + android:layout_gravity="right|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot_right" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + + </FrameLayout> + </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml index dde1248356e0..720e47b1908c 100644 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml @@ -26,6 +26,24 @@ android:layout_gravity="left|bottom" android:tint="#ff000000" android:src="@drawable/rounded_corner_bottom"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="0dp" + android:layout_gravity="left|bottom" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|right" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +51,21 @@ android:tint="#ff000000" android:layout_gravity="right|bottom" android:src="@drawable/rounded_corner_bottom"/> + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@dimen/status_bar_height" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="0dp" + android:layout_gravity="right|bottom" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|left" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + </com.android.systemui.RegionInterceptingFrameLayout> diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml index 813c97d06f57..6abe406e0ea6 100644 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ b/packages/SystemUI/res/layout/rounded_corners_top.xml @@ -26,6 +26,24 @@ android:layout_gravity="left|top" android:tint="#ff000000" android:src="@drawable/rounded_corner_top"/> + + <FrameLayout + android:id="@+id/privacy_dot_left_container" + android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginLeft="0dp" + android:layout_gravity="left|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|right" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + </FrameLayout> + <ImageView android:id="@+id/right" android:layout_width="12dp" @@ -33,4 +51,24 @@ android:tint="#ff000000" android:layout_gravity="right|top" android:src="@drawable/rounded_corner_top"/> + + <FrameLayout + android:id="@+id/privacy_dot_right_container" + android:layout_height="@*android:dimen/status_bar_height_portrait" + android:layout_width="wrap_content" + android:layout_marginTop="@dimen/status_bar_padding_top" + android:layout_marginRight="0dp" + android:layout_gravity="right|top" + android:visibility="invisible" > + <ImageView + android:id="@+id/privacy_dot" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|left" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="visible" /> + + </FrameLayout> + + </com.android.systemui.RegionInterceptingFrameLayout> 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/layout/system_event_animation_window.xml b/packages/SystemUI/res/layout/system_event_animation_window.xml new file mode 100644 index 000000000000..c92dec9dd643 --- /dev/null +++ b/packages/SystemUI/res/layout/system_event_animation_window.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|end" + android:paddingTop="@dimen/status_bar_padding_top" + android:paddingEnd="8dp" + > + + <ImageView + android:id="@+id/dot_view" + android:layout_width="10dp" + android:layout_height="10dp" + android:layout_gravity="center_vertical|end" + android:src="@drawable/system_animation_ongoing_dot" + android:visibility="invisible" + /> + +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index 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..5d9c909ef4c7 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> @@ -1398,6 +1397,7 @@ <dimen name="max_people_avatar_size_for_large_content">64dp</dimen> <dimen name="max_people_avatar_size">108dp</dimen> <dimen name="name_text_size_for_small">14sp</dimen> + <dimen name="name_text_size_for_medium">14sp</dimen> <dimen name="name_text_size_for_large">24sp</dimen> <dimen name="content_text_size_for_medium">12sp</dimen> <dimen name="content_text_size_for_large">14sp</dimen> @@ -1415,17 +1415,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/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 983b1bb63913..62d5a458d51d 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -91,6 +91,13 @@ public class ActivityStarterDelegate implements ActivityStarter { } @Override + public void startActivity(Intent intent, boolean dismissShade, + @Nullable ActivityLaunchAnimator.Controller animationController) { + mActualStarter.ifPresent( + starter -> starter.get().startActivity(intent, dismissShade, animationController)); + } + + @Override public void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade) { mActualStarter.ifPresent( starter -> starter.get().startActivity(intent, onlyProvisioned, dismissShade)); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index a686fc086b40..cbfdce5d0c69 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -79,6 +79,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationViewHierarchyManager; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.VibratorHelper; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; import com.android.systemui.statusbar.notification.NotificationFilter; @@ -352,6 +354,8 @@ public class Dependency { @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy; @Inject Lazy<NavigationBarOverlayController> mNavbarButtonsControllerLazy; @Inject Lazy<TelephonyListenerManager> mTelephonyListenerManager; + @Inject Lazy<SystemStatusAnimationScheduler> mSystemStatusAnimationSchedulerLazy; + @Inject Lazy<PrivacyDotViewController> mPrivacyDotViewControllerLazy; @Inject public Dependency() { @@ -561,6 +565,10 @@ public class Dependency { mProviders.put(NavigationBarOverlayController.class, mNavbarButtonsControllerLazy::get); + mProviders.put(SystemStatusAnimationScheduler.class, + mSystemStatusAnimationSchedulerLazy::get); + mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get); + Dependency.setInstance(this); } diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 5fa98bccfc69..d07723e475fb 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -88,6 +88,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.SecureSetting; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.settings.SecureSettings; @@ -126,6 +127,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private DisplayManager.DisplayListener mDisplayListener; private CameraAvailabilityListener mCameraListener; private final UserTracker mUserTracker; + private final PrivacyDotViewController mDotViewController; //TODO: These are piecemeal being updated to Points for now to support non-square rounded // corners. for now it is only supposed when reading the intrinsic size from the drawables with @@ -140,6 +142,11 @@ public class ScreenDecorations extends SystemUI implements Tunable { protected View[] mOverlays; @Nullable private DisplayCutoutView[] mCutoutViews; + //TODO: + View mTopLeftDot; + View mTopRightDot; + View mBottomLeftDot; + View mBottomRightDot; private float mDensity; private WindowManager mWindowManager; private int mRotation; @@ -147,6 +154,8 @@ public class ScreenDecorations extends SystemUI implements Tunable { private Handler mHandler; private boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; + private int mStatusBarHeightPortrait; + private int mStatusBarHeightLandscape; private CameraAvailabilityListener.CameraTransitionCallback mCameraTransitionCallback = new CameraAvailabilityListener.CameraTransitionCallback() { @@ -205,13 +214,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { SecureSettings secureSettings, BroadcastDispatcher broadcastDispatcher, TunerService tunerService, - UserTracker userTracker) { + UserTracker userTracker, + PrivacyDotViewController dotViewController) { super(context); mMainHandler = handler; mSecureSettings = secureSettings; mBroadcastDispatcher = broadcastDispatcher; mTunerService = tunerService; mUserTracker = userTracker; + mDotViewController = dotViewController; } @Override @@ -222,6 +233,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { } mHandler = startHandlerThread(); mHandler.post(this::startOnScreenDecorationsThread); + mDotViewController.setUiExecutor(mHandler::post); } @VisibleForTesting @@ -286,6 +298,7 @@ public class ScreenDecorations extends SystemUI implements Tunable { private void setupDecorations() { if (hasRoundedCorners() || shouldDrawCutout()) { + updateStatusBarHeight(); final DisplayCutout cutout = getCutout(); final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); int rotatedPos; @@ -298,6 +311,10 @@ public class ScreenDecorations extends SystemUI implements Tunable { removeOverlay(i); } } + // Overlays have been created, send the dots to the controller + //TODO: need a better way to do this + mDotViewController.initialize( + mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot); } else { removeAllOverlays(); } @@ -431,14 +448,21 @@ public class ScreenDecorations extends SystemUI implements Tunable { private View overlayForPosition(@BoundsPosition int pos) { switch (pos) { case BOUNDS_POSITION_TOP: - return LayoutInflater.from(mContext) + case BOUNDS_POSITION_LEFT: + View top = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners_top, null); + mTopLeftDot = top.findViewById(R.id.privacy_dot_left_container); + mTopRightDot = top.findViewById(R.id.privacy_dot_right_container); + return top; case BOUNDS_POSITION_BOTTOM: - return LayoutInflater.from(mContext) + case BOUNDS_POSITION_RIGHT: + View bottom = LayoutInflater.from(mContext) .inflate(R.layout.rounded_corners_bottom, null); + mBottomLeftDot = bottom.findViewById(R.id.privacy_dot_left_container); + mBottomRightDot = bottom.findViewById(R.id.privacy_dot_right_container); + return bottom; default: - return LayoutInflater.from(mContext) - .inflate(R.layout.rounded_corners, null); + throw new IllegalArgumentException("Unknown bounds position"); } } @@ -575,6 +599,11 @@ public class ScreenDecorations extends SystemUI implements Tunable { View child; for (int j = 0; j < size; j++) { child = ((ViewGroup) mOverlays[i]).getChildAt(j); + if (child.getId() == R.id.privacy_dot_left_container + || child.getId() == R.id.privacy_dot_right_container) { + // Exclude privacy dot from color inversion (for now?) + continue; + } if (child instanceof ImageView) { ((ImageView) child).setImageTintList(tintList); } else if (child instanceof DisplayCutoutView) { @@ -611,10 +640,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { Preconditions.checkState(mHandler.getLooper().getThread() == Thread.currentThread(), "must call on " + mHandler.getLooper().getThread() + ", but was " + Thread.currentThread()); + + int newRotation = mContext.getDisplay().getRotation(); + if (mRotation != newRotation) { + mDotViewController.updateRotation(newRotation); + } + if (mPendingRotationChange) { return; } - int newRotation = mContext.getDisplay().getRotation(); if (newRotation != mRotation) { mRotation = newRotation; @@ -630,6 +664,14 @@ public class ScreenDecorations extends SystemUI implements Tunable { } } + private void updateStatusBarHeight() { + mStatusBarHeightLandscape = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_landscape); + mStatusBarHeightPortrait = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_portrait); + mDotViewController.setStatusBarHeights(mStatusBarHeightPortrait, mStatusBarHeightLandscape); + } + private void updateRoundedCornerRadii() { // We should eventually move to just using the intrinsic size of the drawables since // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java index 2661d8913a3f..b544599d0979 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsController.java @@ -60,17 +60,31 @@ public interface AppOpsController { /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * - * @return List of active AppOps information + * @return List of active AppOps information, without paused elements. */ List<AppOpItem> getActiveAppOps(); /** + * Returns a copy of the list containing all the active AppOps that the controller tracks. + * + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. + * @return List of active AppOps information + */ + List<AppOpItem> getActiveAppOps(boolean showPaused); + + /** * Returns a copy of the list containing all the active AppOps that the controller tracks, for * a given user id. * * @param userId User id to track + * @param showPaused {@code true} to also obtain paused items. {@code false} otherwise. * * @return List of active AppOps information for that user id */ - List<AppOpItem> getActiveAppOpsForUser(int userId); + List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused); + + /** + * @return whether this controller is considering the microphone as muted. + */ + boolean isMicMuted(); } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 994401db4380..534f93ec0e47 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -241,9 +241,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName); if (item == null && active) { item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime()); - if (code == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(code)) { item.setDisabled(isAnyRecordingPausedLocked(uid)); - } else if (code == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(code)) { item.setDisabled(mCameraDisabled); } mActiveItems.add(item); @@ -298,6 +298,11 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon return PermissionManager.shouldShowPackageForIndicatorCached(mContext, packageName); } + @WorkerThread + public List<AppOpItem> getActiveAppOps() { + return getActiveAppOps(false); + } + /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * @@ -306,8 +311,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information */ @WorkerThread - public List<AppOpItem> getActiveAppOps() { - return getActiveAppOpsForUser(UserHandle.USER_ALL); + public List<AppOpItem> getActiveAppOps(boolean showPaused) { + return getActiveAppOpsForUser(UserHandle.USER_ALL, showPaused); } /** @@ -321,7 +326,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon * @return List of active AppOps information for that user id */ @WorkerThread - public List<AppOpItem> getActiveAppOpsForUser(int userId) { + public List<AppOpItem> getActiveAppOpsForUser(int userId, boolean showPaused) { Assert.isNotMainThread(); List<AppOpItem> list = new ArrayList<>(); synchronized (mActiveItems) { @@ -330,7 +335,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId) - && isUserVisible(item.getPackageName()) && !item.isDisabled()) { + && isUserVisible(item.getPackageName()) + && (showPaused || !item.isDisabled())) { list.add(item); } } @@ -441,9 +447,9 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon AppOpItem item = mActiveItems.get(i); boolean paused = false; - if (item.getCode() == AppOpsManager.OP_RECORD_AUDIO) { + if (isOpMicrophone(item.getCode())) { paused = isAnyRecordingPausedLocked(item.getUid()); - } else if (item.getCode() == AppOpsManager.OP_CAMERA) { + } else if (isOpCamera(item.getCode())) { paused = mCameraDisabled; } @@ -502,6 +508,19 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon }); } + @Override + public boolean isMicMuted() { + return mMicMuted; + } + + private boolean isOpCamera(int op) { + return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA; + } + + private boolean isOpMicrophone(int op) { + return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE; + } + protected class H extends Handler { H(Looper looper) { super(looper); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 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/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index d224b63bc96d..a16037981370 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -34,9 +34,6 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.icu.text.MeasureFormat; -import android.icu.util.Measure; -import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; @@ -63,7 +60,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.text.SimpleDateFormat; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -71,7 +67,6 @@ import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -83,9 +78,6 @@ public class PeopleSpaceUtils { /** Turns on debugging information about People Space. */ public static final boolean DEBUG = true; private static final String TAG = "PeopleSpaceUtils"; - private static final int DAYS_IN_A_WEEK = 7; - private static final int MIN_HOUR = 1; - private static final int ONE_DAY = 1; public static final String PACKAGE_NAME = "package_name"; public static final String USER_ID = "user_id"; public static final String SHORTCUT_ID = "shortcut_id"; @@ -374,36 +366,6 @@ public class PeopleSpaceUtils { return bitmap; } - /** Returns a readable status describing the {@code lastInteraction}. */ - public static String getLastInteractionString(Context context, long lastInteraction) { - if (lastInteraction == 0L) { - Log.e(TAG, "Could not get valid last interaction"); - return context.getString(R.string.basic_status); - } - long now = System.currentTimeMillis(); - Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction); - MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), - MeasureFormat.FormatWidth.WIDE); - if (durationSinceLastInteraction.toHours() < MIN_HOUR) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toMinutes(), MeasureUnit.MINUTE))); - } else if (durationSinceLastInteraction.toDays() < ONE_DAY) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toHours(), - MeasureUnit.HOUR))); - } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) { - return context.getString(R.string.timestamp, formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toHours(), - MeasureUnit.DAY))); - } else { - return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK - ? R.string.timestamp : R.string.over_timestamp, - formatter.formatMeasures( - new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK, - MeasureUnit.WEEK))); - } - } - /** * Returns whether the {@code conversation} should be kept for display in the People Space. * diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 51af47d92d7b..6b917c576fd8 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -33,7 +33,6 @@ import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH; import static com.android.systemui.people.PeopleSpaceUtils.convertDrawableToBitmap; import static com.android.systemui.people.PeopleSpaceUtils.getUserId; -import android.annotation.ColorInt; import android.annotation.Nullable; import android.app.PendingIntent; import android.app.people.ConversationStatus; @@ -41,27 +40,28 @@ import android.app.people.PeopleSpaceTile; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.icu.text.MeasureFormat; +import android.icu.util.Measure; +import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; -import android.view.ContextThemeWrapper; import android.view.View; import android.widget.RemoteViews; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; -import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.text.NumberFormat; +import java.time.Duration; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -76,6 +76,10 @@ class PeopleTileViewHelper { public static final boolean DEBUG = true; private static final String TAG = "PeopleTileView"; + private static final int DAYS_IN_A_WEEK = 7; + private static final int ONE_DAY = 1; + private static final int MAX_WEEKS = 2; + public static final int LAYOUT_SMALL = 0; public static final int LAYOUT_MEDIUM = 1; public static final int LAYOUT_LARGE = 2; @@ -83,7 +87,9 @@ class PeopleTileViewHelper { private static final int MIN_CONTENT_MAX_LINES = 2; private static final int FIXED_HEIGHT_DIMENS_FOR_LARGE_CONTENT = 14 + 12 + 4 + 16; - private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT = 8 + 4 + 4 + 8; + private static final int MIN_MEDIUM_VERTICAL_PADDING = 4; + private static final int MAX_MEDIUM_PADDING = 16; + private static final int FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING = 4 + 4; private static final int FIXED_HEIGHT_DIMENS_FOR_SMALL = 6 + 4 + 8; private static final int FIXED_WIDTH_DIMENS_FOR_SMALL = 4 + 4; @@ -96,6 +102,8 @@ class PeopleTileViewHelper { public static final String EMPTY_STRING = ""; + private int mMediumVerticalPadding; + private Context mContext; private PeopleSpaceTile mTile; private float mDensity; @@ -205,7 +213,8 @@ class PeopleTileViewHelper { private int getContentHeightForLayout(int lineHeight) { switch (mLayoutSize) { case LAYOUT_MEDIUM: - return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT); + return mHeight - (lineHeight + FIXED_HEIGHT_DIMENS_FOR_MEDIUM_CONTENT_BEFORE_PADDING + + mMediumVerticalPadding * 2); case LAYOUT_LARGE: return mHeight - (getSizeInDp( R.dimen.max_people_avatar_size_for_large_content) + lineHeight @@ -224,7 +233,16 @@ class PeopleTileViewHelper { } // Small layout used below a certain minimum mWidth with any mHeight. if (mWidth >= getSizeInDp(R.dimen.required_width_for_medium)) { - if (DEBUG) Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight); + int spaceAvailableForPadding = + mHeight - (getSizeInDp(R.dimen.avatar_size_for_medium) + 4 + getLineHeight( + getSizeInDp(R.dimen.name_text_size_for_medium))); + if (DEBUG) { + Log.d(TAG, "Medium view for mWidth: " + mWidth + " mHeight: " + mHeight + + " with padding space: " + spaceAvailableForPadding); + } + int maxVerticalPadding = Math.min(Math.floorDiv(spaceAvailableForPadding, 2), + MAX_MEDIUM_PADDING); + mMediumVerticalPadding = Math.max(MIN_MEDIUM_VERTICAL_PADDING, maxVerticalPadding); return LAYOUT_MEDIUM; } // Small layout can always handle our minimum mWidth and mHeight for our widget. @@ -347,11 +365,7 @@ class PeopleTileViewHelper { setMaxLines(views); CharSequence content = mTile.getNotificationContent(); views = setPunctuationRemoteViewsFields(views, content); - // TODO(b/184931139): Update to RemoteViews wrapper to set via attribute once available - @ColorInt int color = Utils.getColorAttr(mContext, - android.R.attr.textColorPrimary).getDefaultColor(); - views.setInt(R.id.text_content, "setTextColor", color); - + views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorPrimary); views.setTextViewText(R.id.text_content, mTile.getNotificationContent()); views.setViewVisibility(R.id.image, View.GONE); views.setImageViewResource(R.id.predefined_icon, R.drawable.ic_message); @@ -398,9 +412,7 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.messages_count, View.GONE); setMaxLines(views); // Secondary text color for statuses. - @ColorInt int secondaryColor = Utils.getColorAttr(mContext, - android.R.attr.textColorSecondary).getDefaultColor(); - views.setInt(R.id.text_content, "setTextColor", secondaryColor); + views.setColorAttr(R.id.text_content, "setTextColor", android.R.attr.textColorSecondary); views.setTextViewText(R.id.text_content, statusText); Icon statusIcon = status.getIcon(); @@ -541,6 +553,14 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.text_content, View.VISIBLE); views.setViewVisibility(R.id.subtext, View.GONE); } + + if (mLayoutSize == LAYOUT_MEDIUM) { + if (DEBUG) Log.d(TAG, "Set vertical padding: " + mMediumVerticalPadding); + int horizontalPadding = (int) Math.floor(MAX_MEDIUM_PADDING * mDensity); + int verticalPadding = (int) Math.floor(mMediumVerticalPadding * mDensity); + views.setViewPadding(R.id.item, horizontalPadding, verticalPadding, horizontalPadding, + verticalPadding); + } return views; } @@ -551,9 +571,16 @@ class PeopleTileViewHelper { views.setViewVisibility(R.id.predefined_icon, View.GONE); views.setViewVisibility(R.id.messages_count, View.GONE); } - String status = PeopleSpaceUtils.getLastInteractionString(mContext, + String status = getLastInteractionString(mContext, mTile.getLastInteractionTimestamp()); - views.setTextViewText(R.id.last_interaction, status); + if (status != null) { + if (DEBUG) Log.d(TAG, "Show last interaction"); + views.setViewVisibility(R.id.last_interaction, View.VISIBLE); + views.setTextViewText(R.id.last_interaction, status); + } else { + if (DEBUG) Log.d(TAG, "Hide last interaction"); + views.setViewVisibility(R.id.last_interaction, View.GONE); + } return views; } @@ -599,4 +626,34 @@ class PeopleTileViewHelper { hasNewStory); return convertDrawableToBitmap(personDrawable); } + + /** Returns a readable status describing the {@code lastInteraction}. */ + @Nullable + public static String getLastInteractionString(Context context, long lastInteraction) { + if (lastInteraction == 0L) { + Log.e(TAG, "Could not get valid last interaction"); + return null; + } + long now = System.currentTimeMillis(); + Duration durationSinceLastInteraction = Duration.ofMillis(now - lastInteraction); + MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(), + MeasureFormat.FormatWidth.WIDE); + if (durationSinceLastInteraction.toDays() <= ONE_DAY) { + return null; + } else if (durationSinceLastInteraction.toDays() < DAYS_IN_A_WEEK) { + return context.getString(R.string.timestamp, formatter.formatMeasures( + new Measure(durationSinceLastInteraction.toHours(), + MeasureUnit.DAY))); + } else if (durationSinceLastInteraction.toDays() <= DAYS_IN_A_WEEK * 2) { + return context.getString(durationSinceLastInteraction.toDays() == DAYS_IN_A_WEEK + ? R.string.timestamp : R.string.over_timestamp, + formatter.formatMeasures( + new Measure(durationSinceLastInteraction.toDays() / DAYS_IN_A_WEEK, + MeasureUnit.WEEK))); + } else { + // Over 2 weeks ago + return context.getString(R.string.over_timestamp, + formatter.formatMeasures(new Measure(MAX_WEEKS, MeasureUnit.WEEK))); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt index f87ea7c61ca8..feb27d804e5e 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt @@ -29,6 +29,7 @@ import android.util.Log import androidx.annotation.MainThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread +import com.android.systemui.appops.AppOpsController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -48,7 +49,6 @@ private val defaultDialogProvider = object : PrivacyDialogController.DialogProvi return PrivacyDialog(context, list, starter) } } - /** * Controller for [PrivacyDialog]. * @@ -66,6 +66,7 @@ class PrivacyDialogController( private val uiExecutor: Executor, private val privacyLogger: PrivacyLogger, private val keyguardStateController: KeyguardStateController, + private val appOpsController: AppOpsController, @VisibleForTesting private val dialogProvider: DialogProvider ) { @@ -79,7 +80,8 @@ class PrivacyDialogController( @Background backgroundExecutor: Executor, @Main uiExecutor: Executor, privacyLogger: PrivacyLogger, - keyguardStateController: KeyguardStateController + keyguardStateController: KeyguardStateController, + appOpsController: AppOpsController ) : this( permissionManager, packageManager, @@ -90,6 +92,7 @@ class PrivacyDialogController( uiExecutor, privacyLogger, keyguardStateController, + appOpsController, defaultDialogProvider ) @@ -127,7 +130,9 @@ class PrivacyDialogController( } @WorkerThread - private fun permGroupUsage(): List<PermGroupUsage> = permissionManager.indicatorAppOpUsageData + private fun permGroupUsage(): List<PermGroupUsage> { + return permissionManager.getIndicatorAppOpUsageData(appOpsController.isMicMuted) + } /** * Show the [PrivacyDialog] @@ -261,4 +266,4 @@ class PrivacyDialogController( starter: (String, Int) -> Unit ): PrivacyDialog } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 4c617edd8542..63ec6e5db017 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -54,7 +54,8 @@ private const val UNKNOWN_TIMESTAMP = -1L data class PrivacyItem( val privacyType: PrivacyType, val application: PrivacyApplication, - val timeStampElapsed: Long = UNKNOWN_TIMESTAMP + val timeStampElapsed: Long = UNKNOWN_TIMESTAMP, + val paused: Boolean = false ) { val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " + "$timeStampElapsed)" diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index f7e2a31ce727..8b27b6ecbfd1 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -257,7 +257,7 @@ class PrivacyItemController @Inject constructor( privacyList = emptyList() return } - val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter { + val list = appOpsController.getActiveAppOps(true).filter { UserHandle.getUserId(it.uid) in currentUserIds || it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE || it.code == AppOpsManager.OP_PHONE_CALL_CAMERA @@ -279,7 +279,9 @@ class PrivacyItemController @Inject constructor( // Anything earlier than this timestamp can be removed val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS - val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list } + val mustKeep = privacyList.filter { + it.timeStampElapsed > removeBeforeTime && !(it isIn list) + } // There are items we must keep because they haven't been around for enough time. if (mustKeep.isNotEmpty()) { @@ -291,7 +293,18 @@ class PrivacyItemController @Inject constructor( logger.logPrivacyItemsUpdateScheduled(delay) holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay) } - return list + mustKeep + return list.filter { !it.paused } + mustKeep + } + + /** + * Ignores the paused status to determine if the element is in the list + */ + private infix fun PrivacyItem.isIn(list: List<PrivacyItem>): Boolean { + return list.any { + it.privacyType == privacyType && + it.application == application && + it.timeStampElapsed == timeStampElapsed + } } private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { @@ -308,7 +321,7 @@ class PrivacyItemController @Inject constructor( return null } val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) - return PrivacyItem(type, app, appOpItem.timeStartedElapsed) + return PrivacyItem(type, app, appOpItem.timeStartedElapsed, appOpItem.isDisabled) } interface Callback { diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java index 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/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java index 3467838bd02a..74ae3a698998 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -32,6 +32,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.R; +import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.globalactions.GlobalActionsDialogLite; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -65,6 +66,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme private final MetricsLogger mMetricsLogger; private final FalsingManager mFalsingManager; private final SettingsButton mSettingsButton; + private final View mSettingsButtonContainer; private final TextView mBuildText; private final View mEdit; private final MultiUserSwitch mMultiUserSwitch; @@ -152,6 +154,7 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mFalsingManager = falsingManager; mSettingsButton = mView.findViewById(R.id.settings_button); + mSettingsButtonContainer = mView.findViewById(R.id.settings_button_container); mBuildText = mView.findViewById(R.id.build); mEdit = mView.findViewById(android.R.id.edit); mMultiUserSwitch = mView.findViewById(R.id.multi_user_switch); @@ -258,10 +261,12 @@ public class QSFooterViewController extends ViewController<QSFooterView> impleme mView.disable(state2, isTunerEnabled()); } - private void startSettingsActivity() { + ActivityLaunchAnimator.Controller animationController = + mSettingsButtonContainer != null ? ActivityLaunchAnimator.Controller.fromView( + mSettingsButtonContainer) : null; mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), - true /* dismissShade */); + true /* dismissShade */, animationController); } private boolean isTunerEnabled() { 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/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index dbd6758b090d..b60fd1326cd3 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -48,7 +48,7 @@ public class BrightnessSliderView extends FrameLayout { super(context, attrs); } - // Inflated from quick_settings_brightness_dialog or quick_settings_brightness_dialog_thick + // Inflated from quick_settings_brightness_dialog @Override protected void onFinishInflate() { super.onFinishInflate(); 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/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index f57fd21526b9..f4266a27c6b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; import com.android.systemui.statusbar.phone.NotificationIconContainer; @@ -82,6 +83,9 @@ public class NotificationShelf extends ActivatableNotificationView implements private Rect mClipRect = new Rect(); private int mCutoutHeight; private int mGapHeight; + private int mIndexOfFirstViewInShelf = -1; + private int mIndexOfFirstViewInOverflowingSection = -1; + private NotificationShelfController mController; public NotificationShelf(Context context, AttributeSet attrs) { @@ -159,30 +163,49 @@ public class NotificationShelf extends ActivatableNotificationView implements } /** Update the state of the shelf. */ - public void updateState(AmbientState ambientState) { + public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { ExpandableView lastView = ambientState.getLastVisibleBackgroundChild(); ShelfState viewState = (ShelfState) getViewState(); if (mShowNotificationShelf && lastView != null) { - float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding() - + ambientState.getStackTranslation(); ExpandableViewState lastViewState = lastView.getViewState(); - float viewEnd = lastViewState.yTranslation + lastViewState.height; viewState.copyFrom(lastViewState); + viewState.height = getIntrinsicHeight(); - viewState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - viewState.height, - getFullyClosedTranslation()); viewState.zTranslation = ambientState.getBaseZHeight(); viewState.clipTopAmount = 0; viewState.alpha = 1f - ambientState.getHideAmount(); viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; viewState.hideSensitive = false; viewState.xTranslation = getTranslationX(); + viewState.hasItemsInStableShelf = lastViewState.inShelf; + viewState.firstViewInShelf = algorithmState.firstViewInShelf; + viewState.firstViewInOverflowSection = algorithmState.firstViewInOverflowSection; if (mNotGoneIndex != -1) { viewState.notGoneIndex = Math.min(viewState.notGoneIndex, mNotGoneIndex); } - viewState.hasItemsInStableShelf = lastViewState.inShelf; + viewState.hidden = !mAmbientState.isShadeExpanded() - || mAmbientState.isQsCustomizerShowing(); + || mAmbientState.isQsCustomizerShowing() + || algorithmState.firstViewInShelf == null; + + final int indexOfFirstViewInShelf = algorithmState.visibleChildren.indexOf( + algorithmState.firstViewInShelf); + + if (mAmbientState.isExpansionChanging() + && algorithmState.firstViewInShelf != null + && indexOfFirstViewInShelf > 0) { + + // Show shelf if section before it is showing. + final ExpandableView viewBeforeShelf = algorithmState.visibleChildren.get( + indexOfFirstViewInShelf - 1); + if (viewBeforeShelf.getViewState().hidden) { + viewState.hidden = true; + } + } + + final float stackEnd = ambientState.getStackY() + ambientState.getStackHeight(); + viewState.yTranslation = stackEnd - viewState.height; } else { viewState.hidden = true; viewState.location = ExpandableViewState.LOCATION_GONE; @@ -199,13 +222,11 @@ public class NotificationShelf extends ActivatableNotificationView implements if (!mShowNotificationShelf) { return; } - mShelfIcons.resetViewStates(); float shelfStart = getTranslationY(); float numViewsInShelf = 0.0f; View lastChild = mAmbientState.getLastVisibleBackgroundChild(); mNotGoneIndex = -1; - float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2; // find the first view that doesn't overlap with the shelf int notGoneIndex = 0; int colorOfViewBeforeLast = NO_COLOR; @@ -219,7 +240,7 @@ public class NotificationShelf extends ActivatableNotificationView implements float currentScrollVelocity = mAmbientState.getCurrentScrollVelocity(); boolean scrollingFast = currentScrollVelocity > mScrollFastThreshold || (mAmbientState.isExpansionChanging() - && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); + && Math.abs(mAmbientState.getExpandingVelocity()) > mScrollFastThreshold); boolean expandingAnimated = mAmbientState.isExpansionChanging() && !mAmbientState.isPanelTracking(); int baseZHeight = mAmbientState.getBaseZHeight(); @@ -233,22 +254,37 @@ public class NotificationShelf extends ActivatableNotificationView implements if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { continue; } - float notificationClipEnd; boolean aboveShelf = ViewState.getFinalTranslationZ(child) > baseZHeight || child.isPinned(); boolean isLastChild = child == lastChild; float rowTranslationY = child.getTranslationY(); + + final float inShelfAmount = updateShelfTransformation(i, child, scrollingFast, + expandingAnimated, isLastChild); + + final float stackEnd = mAmbientState.getStackY() + + mAmbientState.getStackHeight(); + // TODO(b/172289889) scale mPaddingBetweenElements with expansion amount if ((isLastChild && !child.isInShelf()) || aboveShelf || backgroundForceHidden) { - notificationClipEnd = shelfStart + getIntrinsicHeight(); + notificationClipEnd = stackEnd; + } else if (mAmbientState.isExpansionChanging()) { + if (mIndexOfFirstViewInOverflowingSection != -1 + && i >= mIndexOfFirstViewInOverflowingSection) { + // Clip notifications in (section overflowing into shelf) to shelf start. + notificationClipEnd = shelfStart - mPaddingBetweenElements; + } else { + // Clip notifications before the section overflowing into shelf + // to stackEnd because we do not show the shelf if the section right before the + // shelf is still hidden. + notificationClipEnd = stackEnd; + } } else { notificationClipEnd = shelfStart - mPaddingBetweenElements; } int clipTop = updateNotificationClipHeight(child, notificationClipEnd, notGoneIndex); clipTopAmount = Math.max(clipTop, clipTopAmount); - final float inShelfAmount = updateShelfTransformation(child, scrollingFast, - expandingAnimated, isLastChild); // If the current row is an ExpandableNotificationRow, update its color, roundedness, // and icon state. if (child instanceof ExpandableNotificationRow) { @@ -314,19 +350,23 @@ public class NotificationShelf extends ActivatableNotificationView implements distanceToGapTop / mGapHeight); previousAnv.setBottomRoundness(firstElementRoundness, false /* don't animate */); - backgroundTop = (int) distanceToGapBottom; } } previousAnv = anv; } } + clipTransientViews(); setClipTopAmount(clipTopAmount); - boolean isHidden = getViewState().hidden || clipTopAmount >= getIntrinsicHeight(); - if (mShowNotificationShelf) { - setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); - } + + boolean isHidden = getViewState().hidden + || clipTopAmount >= getIntrinsicHeight() + || !mShowNotificationShelf + || numViewsInShelf < 1f; + + // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. + setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); setBackgroundTop(backgroundTop); setFirstElementRoundness(firstElementRoundness); mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); @@ -339,11 +379,10 @@ public class NotificationShelf extends ActivatableNotificationView implements continue; } ExpandableNotificationRow row = (ExpandableNotificationRow) child; - updateIconClipAmount(row); updateContinuousClipping(row); } - boolean hideBackground = numViewsInShelf < 1.0f; - setHideBackground(hideBackground || backgroundForceHidden); + boolean hideBackground = isHidden; + setHideBackground(hideBackground); if (mNotGoneIndex == -1) { mNotGoneIndex = notGoneIndex; } @@ -476,10 +515,10 @@ public class NotificationShelf extends ActivatableNotificationView implements /** * @return the amount how much this notification is in the shelf */ - private float updateShelfTransformation(ExpandableView view, boolean scrollingFast, + private float updateShelfTransformation(int i, ExpandableView view, boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) { - // Let calculate how much the view is in the shelf + // Let's calculate how much the view is in the shelf float viewStart = view.getTranslationY(); int fullHeight = view.getActualHeight() + mPaddingBetweenElements; float iconTransformStart = calculateIconTransformationStart(view); @@ -496,15 +535,21 @@ public class NotificationShelf extends ActivatableNotificationView implements transformDistance, view.getMinHeight() - getIntrinsicHeight()); } + float viewEnd = viewStart + fullHeight; float fullTransitionAmount = 0.0f; float iconTransitionAmount = 0.0f; float shelfStart = getTranslationY(); - - if (viewEnd >= shelfStart + if (mAmbientState.isExpansionChanging() && !mAmbientState.isOnKeyguard()) { + // TODO(b/172289889) handle icon placement for notification that is clipped by the shelf + if (mIndexOfFirstViewInShelf != -1 && i >= mIndexOfFirstViewInShelf) { + fullTransitionAmount = 1f; + iconTransitionAmount = 1f; + } + } else if (viewEnd >= shelfStart && (!mAmbientState.isUnlockHintRunning() || view.isInShelf()) && (mAmbientState.isShadeExpanded() - || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { + || (!view.isPinned() && !view.isHeadsUpAnimatingAway()))) { if (viewStart < shelfStart) { float fullAmount = (shelfStart - viewStart) / fullHeight; @@ -572,7 +617,7 @@ public class NotificationShelf extends ActivatableNotificationView implements && !mNoAnimationsInThisFrame; } iconState.clampedAppearAmount = clampedAmount; - setIconTransformationAmount(view, transitionAmount, isLastChild); + setIconTransformationAmount(view, transitionAmount); } private boolean isTargetClipped(ExpandableView view) { @@ -585,12 +630,10 @@ public class NotificationShelf extends ActivatableNotificationView implements + view.getContentTranslation() + view.getRelativeTopPadding(target) + target.getHeight(); - return endOfTarget >= getTranslationY() - mPaddingBetweenElements; } - private void setIconTransformationAmount(ExpandableView view, float transitionAmount, - boolean isLastChild) { + private void setIconTransformationAmount(ExpandableView view, float transitionAmount) { if (!(view instanceof ExpandableNotificationRow)) { return; } @@ -601,7 +644,6 @@ public class NotificationShelf extends ActivatableNotificationView implements return; } iconState.alpha = transitionAmount; - boolean isAppearing = row.isDrawingAppearAnimation() && !row.isInShelf(); iconState.hidden = isAppearing || (view instanceof ExpandableNotificationRow @@ -610,8 +652,8 @@ public class NotificationShelf extends ActivatableNotificationView implements || (transitionAmount == 0.0f && !iconState.isAnimating(icon)) || row.isAboveShelf() || row.showingPulsing() - || (!row.isInShelf() && isLastChild) || row.getTranslationZ() > mAmbientState.getBaseZHeight(); + iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount; // Fade in icons at shelf start @@ -790,8 +832,19 @@ public class NotificationShelf extends ActivatableNotificationView implements mController = notificationShelfController; } + public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) { + mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); + } + + public void setFirstViewInOverflowingSection(ExpandableView firstViewInOverflowingSection) { + mIndexOfFirstViewInOverflowingSection = + mHostLayoutController.indexOfChild(firstViewInOverflowingSection); + } + private class ShelfState extends ExpandableViewState { private boolean hasItemsInStableShelf; + private ExpandableView firstViewInShelf; + private ExpandableView firstViewInOverflowSection; @Override public void applyToView(View view) { @@ -800,6 +853,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.applyToView(view); + setIndexOfFirstViewInShelf(firstViewInShelf); + setFirstViewInOverflowingSection(firstViewInOverflowSection); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); @@ -812,6 +867,8 @@ public class NotificationShelf extends ActivatableNotificationView implements } super.animateTo(child, properties); + setIndexOfFirstViewInShelf(firstViewInShelf); + setFirstViewInOverflowingSection(firstViewInOverflowSection); updateAppearance(); setHasItemsInStableShelf(hasItemsInStableShelf); mShelfIcons.setAnimationsEnabled(mAnimationsEnabled); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java index 1e935c19b710..4f70fdb0d978 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java @@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.StatusBarNotificationPresenter; @@ -103,9 +104,10 @@ public class NotificationShelfController { return mView.getHeight(); } - public void updateState(AmbientState ambientState) { + public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { mAmbientState = ambientState; - mView.updateState(ambientState); + mView.updateState(algorithmState, ambientState); } public int getIntrinsicHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index 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/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt new file mode 100644 index 000000000000..5ab71bc62fe6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt @@ -0,0 +1,352 @@ +/* + * 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.systemui.statusbar.events + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.annotation.UiThread +import android.util.Log +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout + +import com.android.systemui.animation.Interpolators +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main + +import java.lang.IllegalStateException +import java.util.concurrent.Executor +import javax.inject.Inject + +/** + * Understands how to keep the persistent privacy dot in the corner of the screen in + * ScreenDecorations, which does not rotate with the device. + * + * The basic principle here is that each dot will sit in a box that is equal to the margins of the + * status bar (specifically the status_bar_contents view in PhoneStatusBarView). Each dot container + * will have its gravity set towards the corner (i.e., top-right corner gets top|right gravity), and + * the contained ImageView will be set to center_vertical and away from the corner horizontally. The + * Views will match the status bar top padding and status bar height so that the dot can appear to + * reside directly after the status bar system contents (basically to the right of the battery). + * + * NOTE: any operation that modifies views directly must run on the provided executor, because + * these views are owned by ScreenDecorations and it runs in its own thread + */ + +@SysUISingleton +class PrivacyDotViewController @Inject constructor( + @Main val mainExecutor: Executor, + val animationScheduler: SystemStatusAnimationScheduler +) { + private var rotation = 0 + private var leftSize = 0 + private var rightSize = 0 + + private var sbHeightPortrait = 0 + private var sbHeightLandscape = 0 + + private var hasMultipleHeights = false + private var needsHeightUpdate = false + private var needsRotationUpdate = false + private var needsMarginUpdate = false + + private lateinit var tl: View + private lateinit var tr: View + private lateinit var bl: View + private lateinit var br: View + + // Track which corner is active (based on orientation + RTL) + private var designatedCorner: View? = null + + // Privacy dots are created in ScreenDecoration's UiThread, which is not the main thread + private var uiExecutor: Executor? = null + + private val views: Sequence<View> + get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl) + + fun setUiExecutor(e: Executor) { + uiExecutor = e + } + + @UiThread + fun updateRotation(rot: Int) { + if (rot == rotation) { + return + } + + // A rotation has started, hide the views to avoid flicker + setCornerVisibilities(View.INVISIBLE) + + if (hasMultipleHeights && (rotation % 2) != (rot % 2)) { + // we've changed from vertical to horizontal; update status bar height + needsHeightUpdate = true + } + + rotation = rot + needsRotationUpdate = true + } + + @UiThread + private fun updateHeights(rot: Int) { + val height = when (rot) { + 0, 2 -> sbHeightPortrait + 1, 3 -> sbHeightLandscape + else -> 0 + } + + views.forEach { it.layoutParams.height = height } + } + + // Update the gravity and margins of the privacy views + @UiThread + private fun updateRotations() { + // To keep a view in the corner, its gravity is always the description of its current corner + // Therefore, just figure out which view is in which corner. This turns out to be something + // like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and + // rotating the device counter-clockwise increments rotation by 1 + + views.forEach { corner -> + val rotatedCorner = rotatedCorner(cornerForView(corner)) + (corner.layoutParams as FrameLayout.LayoutParams).apply { + gravity = rotatedCorner.toGravity() + } + + // Set the dot's view gravity to hug the status bar + (corner.findViewById<View>(R.id.privacy_dot) + .layoutParams as FrameLayout.LayoutParams) + .gravity = rotatedCorner.innerGravity() + } + } + + @UiThread + private fun updateCornerSizes() { + views.forEach { corner -> + val rotatedCorner = rotatedCorner(cornerForView(corner)) + val w = widthForCorner(rotatedCorner) + Log.d(TAG, "updateCornerSizes: setting (${cornerForView(corner)}) to $w") + (corner.layoutParams as FrameLayout.LayoutParams).width = w + corner.requestLayout() + } + } + + // Designated view will be the one at statusbar's view.END + @UiThread + private fun selectDesignatedCorner(): View? { + if (!this::tl.isInitialized) { + return null + } + + val isRtl = tl.isLayoutRtl + + return when (rotation) { + 0 -> if (isRtl) tl else tr + 1 -> if (isRtl) tr else br + 2 -> if (isRtl) br else bl + 3 -> if (isRtl) bl else tl + else -> throw IllegalStateException("unknown rotation") + } + } + + // Track the current designated corner and maybe animate to a new rotation + @UiThread + private fun updateDesignatedCorner(newCorner: View) { + designatedCorner = newCorner + + if (animationScheduler.hasPersistentDot) { + designatedCorner!!.visibility = View.VISIBLE + designatedCorner!!.alpha = 0f + designatedCorner!!.animate() + .alpha(1.0f) + .setDuration(300) + .start() + } + } + + @UiThread + private fun setCornerVisibilities(vis: Int) { + views.forEach { corner -> + corner.visibility = vis + } + } + + private fun cornerForView(v: View): Int { + return when (v) { + tl -> TOP_LEFT + tr -> TOP_RIGHT + bl -> BOTTOM_LEFT + br -> BOTTOM_RIGHT + else -> throw IllegalArgumentException("not a corner view") + } + } + + private fun rotatedCorner(corner: Int): Int { + var modded = corner - rotation + if (modded < 0) { + modded += 4 + } + + return modded + } + + private fun widthForCorner(corner: Int): Int { + return when (corner) { + TOP_LEFT, BOTTOM_LEFT -> leftSize + TOP_RIGHT, BOTTOM_RIGHT -> rightSize + else -> throw IllegalArgumentException("Unknown corner") + } + } + + fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) { + if (this::tl.isInitialized && this::tr.isInitialized && + this::bl.isInitialized && this::br.isInitialized) { + if (tl == topLeft && tr == topRight && bl == bottomLeft && br == bottomRight) { + return + } + } + + tl = topLeft + tr = topRight + bl = bottomLeft + br = bottomRight + + designatedCorner = selectDesignatedCorner() + mainExecutor.execute { + animationScheduler.addCallback(systemStatusAnimationCallback) + } + } + + /** + * Set the status bar height in portrait and landscape, in pixels. If they are the same you can + * pass the same value twice + */ + fun setStatusBarHeights(portrait: Int, landscape: Int) { + sbHeightPortrait = portrait + sbHeightLandscape = landscape + + hasMultipleHeights = portrait != landscape + } + + /** + * The dot view containers will fill the margin in order to position the dots correctly + * + * @param left the space between the status bar contents and the left side of the screen + * @param right space between the status bar contents and the right side of the screen + */ + fun setStatusBarMargins(left: Int, right: Int) { + leftSize = left + rightSize = right + + needsMarginUpdate = true + + // Margins come after PhoneStatusBarView does a layout pass, and so will always happen + // after rotation changes. It is safe to execute the updates from here + uiExecutor?.execute { + doUpdates(needsRotationUpdate, needsHeightUpdate, needsMarginUpdate) + } + } + + private fun doUpdates(rot: Boolean, height: Boolean, width: Boolean) { + var newDesignatedCorner: View? = null + + if (rot) { + needsRotationUpdate = false + updateRotations() + newDesignatedCorner = selectDesignatedCorner() + } + + if (height) { + needsHeightUpdate = false + updateHeights(rotation) + } + + if (width) { + needsMarginUpdate = false + updateCornerSizes() + } + + if (newDesignatedCorner != null && newDesignatedCorner != designatedCorner) { + updateDesignatedCorner(newDesignatedCorner) + } + } + + private val systemStatusAnimationCallback: SystemStatusAnimationCallback = + object : SystemStatusAnimationCallback { + override fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { + if (designatedCorner == null) { + return null + } + + val alpha = ObjectAnimator.ofFloat( + designatedCorner, "alpha", 0f, 1f) + alpha.duration = DURATION + alpha.interpolator = Interpolators.ALPHA_OUT + alpha.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animator: Animator) { + uiExecutor?.execute { designatedCorner?.visibility = View.VISIBLE } + } + }) + return alpha + } + + override fun onHidePersistentDot(): Animator? { + if (designatedCorner == null) { + return null + } + + val alpha = ObjectAnimator.ofFloat( + designatedCorner, "alpha", 1f, 0f) + alpha.duration = DURATION + alpha.interpolator = Interpolators.ALPHA_OUT + alpha.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animator: Animator) { + uiExecutor?.execute { designatedCorner?.visibility = View.INVISIBLE } + } + }) + alpha.start() + return null + } + } +} + +const val TOP_LEFT = 0 +const val TOP_RIGHT = 1 +const val BOTTOM_RIGHT = 2 +const val BOTTOM_LEFT = 3 +private const val DURATION = 160L +private const val TAG = "PrivacyDotViewController" + +private fun Int.toGravity(): Int { + return when (this) { + TOP_LEFT -> Gravity.TOP or Gravity.LEFT + TOP_RIGHT -> Gravity.TOP or Gravity.RIGHT + BOTTOM_LEFT -> Gravity.BOTTOM or Gravity.LEFT + BOTTOM_RIGHT -> Gravity.BOTTOM or Gravity.RIGHT + else -> throw IllegalArgumentException("Not a corner") + } +} + +private fun Int.innerGravity(): Int { + return when (this) { + TOP_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT + TOP_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT + BOTTOM_LEFT -> Gravity.CENTER_VERTICAL or Gravity.RIGHT + BOTTOM_RIGHT -> Gravity.CENTER_VERTICAL or Gravity.LEFT + else -> throw IllegalArgumentException("Not a corner") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt new file mode 100644 index 000000000000..6380dc0b9b46 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.events + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.View +import android.widget.ImageView +import com.android.settingslib.graph.ThemedBatteryDrawable +import com.android.systemui.R +import com.android.systemui.privacy.OngoingPrivacyChip +import com.android.systemui.privacy.PrivacyItem + +interface StatusEvent { + val priority: Int + // Whether or not to force the status bar open and show a dot + val forceVisible: Boolean + val viewCreator: (context: Context) -> View +} + +class BatteryEvent : StatusEvent { + override val priority = 50 + override val forceVisible = false + + override val viewCreator: (context: Context) -> View = { context -> + val iv = ImageView(context) + iv.setImageDrawable(ThemedBatteryDrawable(context, Color.WHITE)) + iv.setBackgroundDrawable(ColorDrawable(Color.GREEN)) + iv + } + + override fun toString(): String { + return javaClass.simpleName + } +} +class PrivacyEvent : StatusEvent { + override val priority = 100 + override val forceVisible = true + var privacyItems: List<PrivacyItem> = listOf() + + override val viewCreator: (context: Context) -> View = { context -> + val v = LayoutInflater.from(context) + .inflate(R.layout.ongoing_privacy_chip, null) as OngoingPrivacyChip + v.privacyList = privacyItems + v + } + + override fun toString(): String { + return javaClass.simpleName + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt new file mode 100644 index 000000000000..620963060b49 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt @@ -0,0 +1,137 @@ +/* + * 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.systemui.statusbar.events + +import android.animation.ValueAnimator +import android.content.Context +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.widget.FrameLayout + +import com.android.systemui.R +import com.android.systemui.statusbar.SuperStatusBarViewFactory +import com.android.systemui.statusbar.phone.StatusBarWindowController +import com.android.systemui.statusbar.phone.StatusBarWindowView + +import javax.inject.Inject + +/** + * //TODO: this _probably_ doesn't control a window anymore + * Controls the window for system event animations. + */ +class SystemEventChipAnimationController @Inject constructor( + private val context: Context, + private val statusBarViewFactory: SuperStatusBarViewFactory, + private val statusBarWindowController: StatusBarWindowController +) : SystemStatusChipAnimationCallback { + var showPersistentDot = false + set(value) { + field = value + statusBarWindowController.setForceStatusBarVisible(value) + maybeUpdateShowDot() + } + + private lateinit var animationWindowView: FrameLayout + private lateinit var animationDotView: View + private lateinit var statusBarWindowView: StatusBarWindowView + private var currentAnimatedView: View? = null + + // TODO: move to dagger + private var initialized = false + + override fun onChipAnimationStart( + viewCreator: (context: Context) -> View, + @SystemAnimationState state: Int + ) { + if (!initialized) init() + + if (state == ANIMATING_IN) { + currentAnimatedView = viewCreator(context) + animationWindowView.addView(currentAnimatedView, layoutParamsDefault) + + // We are animating IN; chip comes in from View.END + currentAnimatedView?.apply { + translationX = width.toFloat() + alpha = 0f + visibility = View.VISIBLE + } + } else { + // We are animating away + currentAnimatedView?.apply { + translationX = 0f + alpha = 1f + } + } + } + + override fun onChipAnimationEnd(@SystemAnimationState state: Int) { + if (state == ANIMATING_IN) { + // Finished animating in + currentAnimatedView?.apply { + translationX = 0f + alpha = 1f + } + } else { + // Finished animating away + currentAnimatedView?.apply { + visibility = View.INVISIBLE + } + animationWindowView.removeView(currentAnimatedView) + } + } + + override fun onChipAnimationUpdate( + animator: ValueAnimator, + @SystemAnimationState state: Int + ) { + // Alpha is parameterized 0,1, and translation from (width, 0) + currentAnimatedView?.apply { + val amt = animator.animatedValue as Float + + alpha = amt + + val w = width + val translation = (1 - amt) * w + translationX = translation + } + } + + private fun maybeUpdateShowDot() { + if (!initialized) return + if (!showPersistentDot && currentAnimatedView == null) { + animationDotView.visibility = View.INVISIBLE + } + } + + private fun init() { + initialized = true + statusBarWindowView = statusBarViewFactory.statusBarWindowView + animationWindowView = LayoutInflater.from(context) + .inflate(R.layout.system_event_animation_window, null) as FrameLayout + animationDotView = animationWindowView.findViewById(R.id.dot_view) + val lp = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + lp.gravity = Gravity.END or Gravity.CENTER_VERTICAL + statusBarWindowView.addView(animationWindowView, lp) + } + + private val layoutParamsDefault = FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT).also { + it.gravity = Gravity.END or Gravity.CENTER_VERTICAL + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt new file mode 100644 index 000000000000..b4818233aea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt @@ -0,0 +1,112 @@ +/* + * 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.systemui.statusbar.events + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.privacy.PrivacyItem +import com.android.systemui.privacy.PrivacyItemController +import com.android.systemui.statusbar.policy.BatteryController +import javax.inject.Inject + +/** + * Listens for system events (battery, privacy, connectivity) and allows listeners + * to show status bar animations when they happen + */ +@SysUISingleton +class SystemEventCoordinator @Inject constructor( + private val batteryController: BatteryController, + private val privacyController: PrivacyItemController +) { + private lateinit var scheduler: SystemStatusAnimationScheduler + + fun startObserving() { + /* currently unused + batteryController.addCallback(batteryStateListener) + */ + privacyController.addCallback(privacyStateListener) + } + + fun stopObserving() { + /* currently unused + batteryController.removeCallback(batteryStateListener) + */ + privacyController.removeCallback(privacyStateListener) + } + + fun attachScheduler(s: SystemStatusAnimationScheduler) { + this.scheduler = s + } + + fun notifyPluggedIn() { + scheduler.onStatusEvent(BatteryEvent()) + } + + fun notifyPrivacyItemsEmpty() { + scheduler.setShouldShowPersistentPrivacyIndicator(false) + } + + fun notifyPrivacyItemsChanged() { + val event = PrivacyEvent() + event.privacyItems = privacyStateListener.currentPrivacyItems + scheduler.onStatusEvent(event) + } + + private val batteryStateListener = object : BatteryController.BatteryStateChangeCallback { + var plugged = false + var stateKnown = false + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (!stateKnown) { + stateKnown = true + plugged = pluggedIn + notifyListeners() + return + } + + if (plugged != pluggedIn) { + plugged = pluggedIn + notifyListeners() + } + } + + private fun notifyListeners() { + // We only care about the plugged in status + if (plugged) notifyPluggedIn() + } + } + + private val privacyStateListener = object : PrivacyItemController.Callback { + var currentPrivacyItems = listOf<PrivacyItem>() + + override fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) { + if (privacyItems.isNotEmpty() && currentPrivacyItems.containsAll(privacyItems)) { + return + } + currentPrivacyItems = privacyItems + notifyListeners() + } + + private fun notifyListeners() { + if (currentPrivacyItems.isEmpty()) { + notifyPrivacyItemsEmpty() + } else { + notifyPrivacyItemsChanged() + } + } + } +} + +private const val TAG = "SystemEventCoordinator"
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt new file mode 100644 index 000000000000..b85d0310092c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt @@ -0,0 +1,333 @@ +/* + * 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.systemui.statusbar.events + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ValueAnimator +import android.annotation.IntDef +import android.content.Context +import android.os.Process +import android.util.Log +import android.view.View + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.phone.StatusBarWindowController +import com.android.systemui.statusbar.policy.CallbackController +import com.android.systemui.util.Assert +import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.util.time.SystemClock + +import javax.inject.Inject + +/** + * Dead-simple scheduler for system status events. Obeys the following principles (all values TBD): + * - Avoiding log spam by only allowing 12 events per minute (1event/5s) + * - Waits 100ms to schedule any event for debouncing/prioritization + * - Simple prioritization: Privacy > Battery > connectivity (encoded in StatusEvent) + * - Only schedules a single event, and throws away lowest priority events + * + * There are 4 basic stages of animation at play here: + * 1. System chrome animation OUT + * 2. Chip animation IN + * 3. Chip animation OUT; potentially into a dot + * 4. System chrome animation IN + * + * Thus we can keep all animations synchronized with two separate ValueAnimators, one for system + * chrome and the other for the chip. These can animate from 0,1 and listeners can parameterize + * their respective views based on the progress of the animator. Interpolation differences TBD + */ +@SysUISingleton +class SystemStatusAnimationScheduler @Inject constructor( + private val coordinator: SystemEventCoordinator, + private val chipAnimationController: SystemEventChipAnimationController, + private val statusBarWindowController: StatusBarWindowController, + private val systemClock: SystemClock, + @Main private val executor: DelayableExecutor +) : CallbackController<SystemStatusAnimationCallback> { + + /** True from the time a scheduled event starts until it's animation finishes */ + var isActive = false + private set + + @SystemAnimationState var animationState: Int = IDLE + private set + + /** True if the persistent privacy dot should be active */ + var hasPersistentDot = false + private set + + private var scheduledEvent: StatusEvent? = null + private var cancelExecutionRunnable: Runnable? = null + private val listeners = mutableSetOf<SystemStatusAnimationCallback>() + + init { + coordinator.attachScheduler(this) + } + + fun onStatusEvent(event: StatusEvent) { + // Ignore any updates until the system is up and running + if (isTooEarly()) { + return + } + + // Don't deal with threading for now (no need let's be honest) + Assert.isMainThread() + if (event.priority > scheduledEvent?.priority ?: -1) { + if (DEBUG) { + Log.d(TAG, "scheduling event $event") + } + scheduleEvent(event) + } else { + if (DEBUG) { + Log.d(TAG, "ignoring event $event") + } + } + } + + private fun clearDotIfVisible() { + notifyHidePersistentDot() + } + + fun setShouldShowPersistentPrivacyIndicator(should: Boolean) { + if (hasPersistentDot == should) { + return + } + + hasPersistentDot = should + + if (!hasPersistentDot) { + clearDotIfVisible() + } + } + + private fun isTooEarly(): Boolean { + Log.d(TAG, "time=> ${systemClock.uptimeMillis() - Process.getStartUptimeMillis()}") + return systemClock.uptimeMillis() - Process.getStartUptimeMillis() < MIN_UPTIME + } + + /** + * Clear the scheduled event (if any) and schedule a new one + */ + private fun scheduleEvent(event: StatusEvent) { + scheduledEvent = event + if (scheduledEvent!!.forceVisible) { + hasPersistentDot = true + } + + // Schedule the animation to start after a debounce period + cancelExecutionRunnable = executor.executeDelayed({ + cancelExecutionRunnable = null + animationState = ANIMATING_IN + statusBarWindowController.setForceStatusBarVisible(true) + + val entranceAnimator = ValueAnimator.ofFloat(1f, 0f) + entranceAnimator.duration = ENTRANCE_ANIM_LENGTH + entranceAnimator.addListener(systemAnimatorAdapter) + entranceAnimator.addUpdateListener(systemUpdateListener) + + val chipAnimator = ValueAnimator.ofFloat(0f, 1f) + chipAnimator.duration = CHIP_ANIM_LENGTH + chipAnimator.addListener( + ChipAnimatorAdapter(RUNNING_CHIP_ANIM, scheduledEvent!!.viewCreator)) + chipAnimator.addUpdateListener(chipUpdateListener) + + val aSet2 = AnimatorSet() + aSet2.playSequentially(entranceAnimator, chipAnimator) + aSet2.start() + + executor.executeDelayed({ + animationState = ANIMATING_OUT + + val systemAnimator = ValueAnimator.ofFloat(0f, 1f) + systemAnimator.duration = ENTRANCE_ANIM_LENGTH + systemAnimator.addListener(systemAnimatorAdapter) + systemAnimator.addUpdateListener(systemUpdateListener) + + val chipAnimator = ValueAnimator.ofFloat(1f, 0f) + chipAnimator.duration = CHIP_ANIM_LENGTH + chipAnimator.addListener(ChipAnimatorAdapter(IDLE, scheduledEvent!!.viewCreator)) + chipAnimator.addUpdateListener(chipUpdateListener) + + val aSet2 = AnimatorSet() + + aSet2.play(chipAnimator).before(systemAnimator) + if (hasPersistentDot) { + val dotAnim = notifyTransitionToPersistentDot() + if (dotAnim != null) aSet2.playTogether(systemAnimator, dotAnim) + } + + aSet2.start() + + statusBarWindowController.setForceStatusBarVisible(false) + scheduledEvent = null + }, DISPLAY_LENGTH) + }, DELAY) + } + + private fun notifyTransitionToPersistentDot(): Animator? { + val anims: List<Animator> = listeners.mapNotNull { + it.onSystemStatusAnimationTransitionToPersistentDot() + } + if (anims.isNotEmpty()) { + val aSet = AnimatorSet() + aSet.playTogether(anims) + return aSet + } + + return null + } + + private fun notifyHidePersistentDot(): Animator? { + val anims: List<Animator> = listeners.mapNotNull { + it.onHidePersistentDot() + } + + if (anims.isNotEmpty()) { + val aSet = AnimatorSet() + aSet.playTogether(anims) + return aSet + } + + return null + } + + private fun notifySystemStart() { + listeners.forEach { it.onSystemChromeAnimationStart() } + } + + private fun notifySystemFinish() { + listeners.forEach { it.onSystemChromeAnimationEnd() } + } + + private fun notifySystemAnimationUpdate(anim: ValueAnimator) { + listeners.forEach { it.onSystemChromeAnimationUpdate(anim) } + } + + override fun addCallback(listener: SystemStatusAnimationCallback) { + Assert.isMainThread() + + if (listeners.isEmpty()) { + coordinator.startObserving() + } + listeners.add(listener) + } + + override fun removeCallback(listener: SystemStatusAnimationCallback) { + Assert.isMainThread() + + listeners.remove(listener) + if (listeners.isEmpty()) { + coordinator.stopObserving() + } + } + + private val systemUpdateListener = ValueAnimator.AnimatorUpdateListener { + anim -> notifySystemAnimationUpdate(anim) + } + + private val systemAnimatorAdapter = object : AnimatorListenerAdapter() { + override fun onAnimationEnd(p0: Animator?) { + notifySystemFinish() + } + + override fun onAnimationStart(p0: Animator?) { + notifySystemStart() + } + } + + private val chipUpdateListener = ValueAnimator.AnimatorUpdateListener { + anim -> chipAnimationController.onChipAnimationUpdate(anim, animationState) + } + + inner class ChipAnimatorAdapter( + @SystemAnimationState val endState: Int, + val viewCreator: (context: Context) -> View + ) : AnimatorListenerAdapter() { + override fun onAnimationEnd(p0: Animator?) { + chipAnimationController.onChipAnimationEnd(animationState) + animationState = endState + } + + override fun onAnimationStart(p0: Animator?) { + chipAnimationController.onChipAnimationStart(viewCreator, animationState) + } + } +} + +/** + * The general idea here is that this scheduler will run two value animators, and provide + * animator-like callbacks for each kind of animation. The SystemChrome animation is expected to + * create space for the chip animation to display. This means hiding the system elements in the + * status bar and keyguard. + * + * TODO: the chip animation really only has one client, we can probably remove it from this + * interface + * + * The value animators themselves are simple animators from 0.0 to 1.0. Listeners can apply any + * interpolation they choose but realistically these are most likely to be simple alpha transitions + */ +interface SystemStatusAnimationCallback { + @JvmDefault fun onSystemChromeAnimationUpdate(animator: ValueAnimator) {} + @JvmDefault fun onSystemChromeAnimationStart() {} + @JvmDefault fun onSystemChromeAnimationEnd() {} + + // Best method name, change my mind + @JvmDefault fun onSystemStatusAnimationTransitionToPersistentDot(): Animator? { return null } + @JvmDefault fun onHidePersistentDot(): Animator? { return null } +} + +interface SystemStatusChipAnimationCallback { + fun onChipAnimationUpdate(animator: ValueAnimator, @SystemAnimationState state: Int) {} + + fun onChipAnimationStart( + viewCreator: (context: Context) -> View, + @SystemAnimationState state: Int + ) {} + + fun onChipAnimationEnd(@SystemAnimationState state: Int) {} +} + +/** + */ +@Retention(AnnotationRetention.SOURCE) +@IntDef( + value = [ + IDLE, ANIMATING_IN, RUNNING_CHIP_ANIM, ANIMATING_OUT + ] +) +annotation class SystemAnimationState + +/** No animation is in progress */ +const val IDLE = 0 +/** System is animating out, and chip is animating in */ +const val ANIMATING_IN = 1 +/** Chip has animated in and is awaiting exit animation, and optionally playing its own animation */ +const val RUNNING_CHIP_ANIM = 2 +/** Chip is animating away and system is animating back */ +const val ANIMATING_OUT = 3 + +private const val TAG = "SystemStatusAnimationScheduler" +private const val DELAY: Long = 100 +private const val DISPLAY_LENGTH = 5000L +private const val ENTRANCE_ANIM_LENGTH = 500L +private const val CHIP_ANIM_LENGTH = 500L +private const val MIN_UPTIME: Long = 5 * 1000 + +private const val DEBUG = false
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java index 8446b4e6a3f0..caf47207de07 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java @@ -60,7 +60,7 @@ public class AmbientState { private NotificationShelf mShelf; private int mZDistanceBetweenElements; private int mBaseZHeight; - private int mMaxLayoutHeight; + private int mContentHeight; private ExpandableView mLastVisibleBackgroundChild; private float mCurrentScrollVelocity; private int mStatusBarState; @@ -84,6 +84,75 @@ public class AmbientState { private boolean mIsShadeOpening; private float mSectionPadding; + /** Distance of top of notifications panel from top of screen. */ + private float mStackY = 0; + + /** Height of notifications panel. */ + private float mStackHeight = 0; + + /** Fraction of shade expansion. */ + private float mExpansionFraction; + + /** Height of the notifications panel without top padding when expansion completes. */ + private float mStackEndHeight; + + /** + * @return Height of the notifications panel without top padding when expansion completes. + */ + public float getStackEndHeight() { + return mStackEndHeight; + } + + /** + * @param stackEndHeight Height of the notifications panel without top padding + * when expansion completes. + */ + public void setStackEndHeight(float stackEndHeight) { + mStackEndHeight = stackEndHeight; + } + + /** + * @param stackY Distance of top of notifications panel from top of screen. + */ + public void setStackY(float stackY) { + mStackY = stackY; + } + + /** + * @return Distance of top of notifications panel from top of screen. + */ + public float getStackY() { + return mStackY; + } + + /** + * @param expansionFraction Fraction of shade expansion. + */ + public void setExpansionFraction(float expansionFraction) { + mExpansionFraction = expansionFraction; + } + + /** + * @return Fraction of shade expansion. + */ + public float getExpansionFraction() { + return mExpansionFraction; + } + + /** + * @param stackHeight Height of notifications panel. + */ + public void setStackHeight(float stackHeight) { + mStackHeight = stackHeight; + } + + /** + * @return Height of notifications panel. + */ + public float getStackHeight() { + return mStackHeight; + } + /** Tracks the state from AlertingNotificationManager#hasNotifications() */ private boolean mHasAlertEntries; @@ -263,8 +332,8 @@ public class AmbientState { if (mDozeAmount == 1.0f && !isPulseExpanding()) { return mShelf.getHeight(); } - int height = Math.max(mLayoutMinHeight, - Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding); + int height = (int) Math.max(mLayoutMinHeight, + Math.min(mLayoutHeight, mContentHeight) - mTopPadding); if (ignorePulseHeight) { return height; } @@ -313,8 +382,12 @@ public class AmbientState { return mShelf; } - public void setLayoutMaxHeight(int maxLayoutHeight) { - mMaxLayoutHeight = maxLayoutHeight; + public void setContentHeight(int contentHeight) { + mContentHeight = contentHeight; + } + + public float getContentHeight() { + return mContentHeight; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index fb72ac3c1411..733a9f64fb0e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -658,6 +658,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable y = getHeight() - getEmptyBottomMargin(); mDebugPaint.setColor(Color.GREEN); canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + + y = (int) (mAmbientState.getStackY()); + mDebugPaint.setColor(Color.CYAN); + canvas.drawLine(0, y, getWidth(), y, mDebugPaint); + + y = (int) (mAmbientState.getStackY() + mAmbientState.getStackHeight()); + mDebugPaint.setColor(Color.BLUE); + canvas.drawLine(0, y, getWidth(), y, mDebugPaint); } } @@ -1123,18 +1131,37 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mTopPaddingNeedsAnimation = true; mNeedsAnimation = true; } + updateStackPosition(); requestChildrenUpdate(); notifyHeightChangeListener(null, animate); } } /** + * Apply expansion fraction to the y position and height of the notifications panel. + */ + private void updateStackPosition() { + // Consider interpolating from an mExpansionStartY for use on lockscreen and AOD + mAmbientState.setStackY( + MathUtils.lerp(0, mTopPadding, mAmbientState.getExpansionFraction())); + final float shadeBottom = getHeight() - getEmptyBottomMargin(); + mAmbientState.setStackEndHeight(shadeBottom - mTopPadding); + mAmbientState.setStackHeight( + MathUtils.lerp(0, shadeBottom - mTopPadding, mAmbientState.getExpansionFraction())); + } + + /** * Update the height of the panel. * * @param height the expanded height of the panel */ @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void setExpandedHeight(float height) { + final float shadeBottom = getHeight() - getEmptyBottomMargin(); + final float expansionFraction = MathUtils.constrain(height / shadeBottom, 0f, 1f); + mAmbientState.setExpansionFraction(expansionFraction); + updateStackPosition(); + mExpandedHeight = height; setIsExpanded(height > 0); int minExpansionHeight = getMinExpansionHeight(); @@ -2067,7 +2094,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable mContentHeight = height + Math.max(mIntrinsicPadding, mTopPadding) + mBottomMargin; updateScrollability(); clampScrollPosition(); - mAmbientState.setLayoutMaxHeight(mContentHeight); + updateStackPosition(); + mAmbientState.setContentHeight(mContentHeight); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7776e69d9bed..4fc49ed26f64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -788,6 +788,10 @@ public class NotificationStackScrollLayoutController { return mView.getTranslationX(); } + public int indexOfChild(View view) { + return mView.indexOfChild(view); + } + public void setOnHeightChangedListener( ExpandableView.OnHeightChangedListener listener) { mView.setOnHeightChangedListener(listener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java index bbdbe809a8d8..3e1a7816c2b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java @@ -20,21 +20,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; -import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; import com.android.systemui.R; -import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.NotificationShelf; +import com.android.systemui.statusbar.notification.dagger.SilentHeader; import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.FooterView; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * The Algorithm of the {@link com.android.systemui.statusbar.notification.stack @@ -92,20 +93,16 @@ public class StackScrollAlgorithm { // First we reset the view states to their default values. resetChildViewStates(); - initAlgorithmState(mHostView, algorithmState, ambientState); - updatePositionsForState(algorithmState, ambientState); - updateZValuesForState(algorithmState, ambientState); - updateHeadsUpStates(algorithmState, ambientState); updatePulsingStates(algorithmState, ambientState); updateDimmedActivatedHideSensitive(ambientState, algorithmState); updateClipping(algorithmState, ambientState); updateSpeedBumpState(algorithmState, speedBumpIndex); - updateShelfState(ambientState); + updateShelfState(algorithmState, ambientState); getNotificationChildrenStates(algorithmState, ambientState); } @@ -144,10 +141,13 @@ public class StackScrollAlgorithm { } - private void updateShelfState(AmbientState ambientState) { + private void updateShelfState( + StackScrollAlgorithmState algorithmState, + AmbientState ambientState) { + NotificationShelf shelf = ambientState.getShelf(); if (shelf != null) { - shelf.updateState(ambientState); + shelf.updateState(algorithmState, ambientState); } } @@ -172,7 +172,8 @@ public class StackScrollAlgorithm { && ((ExpandableNotificationRow) child).isPinned(); if (mClipNotificationScrollToTop && (!state.inShelf || (isHeadsUp && !firstHeadsUp)) - && newYTranslation < clipStart) { + && newYTranslation < clipStart + && !ambientState.isShadeOpening()) { // The previous view is overlapping on top, clip! float overlapAmount = clipStart - newYTranslation; state.clipTopAmount = (int) overlapAmount; @@ -217,7 +218,6 @@ public class StackScrollAlgorithm { private void initAlgorithmState(ViewGroup hostView, StackScrollAlgorithmState state, AmbientState ambientState) { float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */); - int scrollY = ambientState.getScrollY(); // Due to the overScroller, the stackscroller can have negative scroll state. This is @@ -230,7 +230,6 @@ public class StackScrollAlgorithm { state.visibleChildren.clear(); state.visibleChildren.ensureCapacity(childCount); int notGoneIndex = 0; - ExpandableView lastView = null; for (int i = 0; i < childCount; i++) { ExpandableView v = (ExpandableView) hostView.getChildAt(i); if (v.getVisibility() != View.GONE) { @@ -255,12 +254,101 @@ public class StackScrollAlgorithm { } } } - ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification(); - state.indexOfExpandingNotification = expandingNotification != null - ? expandingNotification.isChildInGroup() - ? state.visibleChildren.indexOf(expandingNotification.getNotificationParent()) - : state.visibleChildren.indexOf(expandingNotification) - : -1; + + state.firstViewInShelf = null; + // Save y, sectionStart, sectionEnd from when shade is fully expanded. + // Consider updating these states in updateContentView instead so that we don't have to + // recalculate in every frame. + float currentY = -scrollY; + int sectionStartIndex = 0; + int sectionEndIndex = 0; + for (int i = 0; i < state.visibleChildren.size(); i++) { + final ExpandableView view = state.visibleChildren.get(i); + // Add space between sections. + final boolean applyGapHeight = childNeedsGapHeight( + ambientState.getSectionProvider(), i, + view, getPreviousView(i, state)); + if (applyGapHeight) { + currentY += mGapHeight; + } + + // Save index of first view in the shelf + final float shelfStart = ambientState.getStackEndHeight() + - ambientState.getShelf().getIntrinsicHeight(); + if (currentY >= shelfStart + && !(view instanceof FooterView) + && state.firstViewInShelf == null) { + state.firstViewInShelf = view; + } + + // Record y position when fully expanded + ExpansionData expansionData = new ExpansionData(); + expansionData.fullyExpandedY = currentY; + state.expansionData.put(view, expansionData); + + if (ambientState.getSectionProvider() + .beginsSection(view, getPreviousView(i, state))) { + + // Save section start/end for views in the section before this new section + ExpandableView sectionStartView = state.visibleChildren.get(sectionStartIndex); + final float sectionStart = + state.expansionData.get(sectionStartView).fullyExpandedY; + + ExpandableView sectionEndView = state.visibleChildren.get(sectionEndIndex); + float sectionEnd = state.expansionData.get(sectionEndView).fullyExpandedY + + sectionEndView.getIntrinsicHeight(); + + // If we show the shelf, trim section end to shelf start + // This means section end > start for views in the shelf + if (state.firstViewInShelf != null && sectionEnd > shelfStart) { + sectionEnd = shelfStart; + } + + // Update section bounds of every view in the previous section + // Consider using shared SectionInfo for views in same section to avoid looping back + for (int j = sectionStartIndex; j < i; j++) { + ExpandableView sectionView = state.visibleChildren.get(j); + ExpansionData viewExpansionData = + state.expansionData.get(sectionView); + viewExpansionData.sectionStart = sectionStart; + viewExpansionData.sectionEnd = sectionEnd; + state.expansionData.put(sectionView, viewExpansionData); + } + sectionStartIndex = i; + + if (view instanceof FooterView) { + // Also record section bounds for FooterView (same as its own) + // because it is the last view and we won't get to this point again + // after the loop ends + ExpansionData footerExpansionData = state.expansionData.get(view); + footerExpansionData.sectionStart = expansionData.fullyExpandedY; + footerExpansionData.sectionEnd = expansionData.fullyExpandedY + + view.getIntrinsicHeight(); + state.expansionData.put(view, footerExpansionData); + } + } + sectionEndIndex = i; + currentY = currentY + + getMaxAllowedChildHeight(view) + + mPaddingBetweenElements; + } + + // Which view starts the section of the view right before the shelf? + // Save it for later when we clip views in that section to shelf start. + state.firstViewInOverflowSection = null; + if (state.firstViewInShelf != null) { + ExpandableView nextView = null; + final int startIndex = state.visibleChildren.indexOf(state.firstViewInShelf); + for (int i = startIndex - 1; i >= 0; i--) { + ExpandableView view = state.visibleChildren.get(i); + if (nextView != null && ambientState.getSectionProvider() + .beginsSection(nextView, view)) { + break; + } + nextView = view; + } + state.firstViewInOverflowSection = nextView; + } } private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex, @@ -272,6 +360,10 @@ public class StackScrollAlgorithm { return notGoneIndex; } + private ExpandableView getPreviousView(int i, StackScrollAlgorithmState algorithmState) { + return i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; + } + /** * Determine the positions for the views. This is the main part of the algorithm. * @@ -288,6 +380,15 @@ public class StackScrollAlgorithm { } } + private void setLocation(ExpandableViewState expandableViewState, float currentYPosition, + int i) { + expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; + if (currentYPosition <= 0) { + expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; + } + } + + // TODO(b/172289889) polish shade open from HUN /** * Populates the {@link ExpandableViewState} for a single child. * @@ -306,53 +407,84 @@ public class StackScrollAlgorithm { StackScrollAlgorithmState algorithmState, AmbientState ambientState, float currentYPosition) { - ExpandableView child = algorithmState.visibleChildren.get(i); - ExpandableView previousChild = i > 0 ? algorithmState.visibleChildren.get(i - 1) : null; + + ExpandableView view = algorithmState.visibleChildren.get(i); + ExpandableViewState viewState = view.getViewState(); + viewState.location = ExpandableViewState.LOCATION_UNKNOWN; + viewState.alpha = 1f - ambientState.getHideAmount(); + + if (view.mustStayOnScreen() && viewState.yTranslation >= 0) { + // Even if we're not scrolled away we're in view and we're also not in the + // shelf. We can relax the constraints and let us scroll off the top! + float end = viewState.yTranslation + viewState.height + ambientState.getStackY(); + viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); + } + + // TODO(b/172289889) move sectionFraction and showSection to initAlgorithmState + // Get fraction of section showing, and later apply it to view height and gaps between views + float sectionFraction = 1f; + boolean showSection = true; + + if (!ambientState.isOnKeyguard() + && !ambientState.isPulseExpanding() + && ambientState.isExpansionChanging()) { + + final ExpansionData expansionData = algorithmState.expansionData.get(view); + final float sectionHeight = expansionData.sectionEnd - expansionData.sectionStart; + sectionFraction = MathUtils.constrain( + (ambientState.getStackHeight() - expansionData.sectionStart) / sectionHeight, + 0f, 1f); + showSection = expansionData.sectionStart < ambientState.getStackHeight(); + } + + // Add gap between sections. final boolean applyGapHeight = childNeedsGapHeight( ambientState.getSectionProvider(), i, - child, previousChild); - ExpandableViewState childViewState = child.getViewState(); - childViewState.location = ExpandableViewState.LOCATION_UNKNOWN; - + view, getPreviousView(i, algorithmState)); if (applyGapHeight) { - currentYPosition += mGapHeight; + currentYPosition += sectionFraction * mGapHeight; } - int childHeight = getMaxAllowedChildHeight(child); - childViewState.yTranslation = currentYPosition; - childViewState.alpha = 1f - ambientState.getHideAmount(); - boolean isFooterView = child instanceof FooterView; - boolean isEmptyShadeView = child instanceof EmptyShadeView; + viewState.yTranslation = currentYPosition; - childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA; - float inset = ambientState.getTopPadding() + ambientState.getStackTranslation() - + ambientState.getSectionPadding(); - if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) { - // Even if we're not scrolled away we're in view and we're also not in the - // shelf. We can relax the constraints and let us scroll off the top! - float end = childViewState.yTranslation + childViewState.height + inset; - childViewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation(); - } - if (isFooterView) { - childViewState.yTranslation = Math.min(childViewState.yTranslation, - ambientState.getInnerHeight() - childHeight); - } else if (isEmptyShadeView) { - childViewState.yTranslation = ambientState.getInnerHeight() - childHeight - + ambientState.getStackTranslation() * 0.25f; - } else if (child != ambientState.getTrackedHeadsUpRow()) { - clampPositionToShelf(child, childViewState, ambientState); + if (view instanceof SectionHeaderView) { + // Add padding before sections for overscroll effect. + viewState.yTranslation += ambientState.getSectionPadding(); } - currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements; - if (currentYPosition <= 0) { - childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP; - } - if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) { - Log.wtf(LOG_TAG, "Failed to assign location for child " + i); + if (view != ambientState.getTrackedHeadsUpRow()) { + if (ambientState.isExpansionChanging()) { + viewState.hidden = !showSection; + viewState.inShelf = algorithmState.firstViewInShelf != null + && i >= algorithmState.visibleChildren.indexOf( + algorithmState.firstViewInShelf) + && !(view instanceof FooterView); + } else { + // When pulsing (incoming notification on AOD), innerHeight is 0; clamp all + // to shelf start, thereby hiding all notifications (except the first one, which we + // later unhide in updatePulsingState) + final int shelfStart = ambientState.getInnerHeight() + - ambientState.getShelf().getIntrinsicHeight(); + if (!(view instanceof FooterView)) { + viewState.yTranslation = Math.min(viewState.yTranslation, shelfStart); + } + if (viewState.yTranslation >= shelfStart) { + viewState.hidden = !view.isExpandAnimationRunning() + && !view.hasExpandingChild() + && !(view instanceof FooterView); + viewState.inShelf = true; + // Notifications in the shelf cannot be visible HUNs. + viewState.headsUpIsVisible = false; + } + } + viewState.height = (int) MathUtils.lerp( + 0, getMaxAllowedChildHeight(view), sectionFraction); } - childViewState.yTranslation += inset; + currentYPosition += viewState.height + sectionFraction * mPaddingBetweenElements; + setLocation(view.getViewState(), currentYPosition, i); + viewState.yTranslation += ambientState.getStackY(); return currentYPosition; } @@ -393,10 +525,10 @@ public class StackScrollAlgorithm { int visibleIndex, View child, View previousChild) { - - boolean needsGapHeight = sectionProvider.beginsSection(child, previousChild) - && visibleIndex > 0; - return needsGapHeight; + return sectionProvider.beginsSection(child, previousChild) + && visibleIndex > 0 + && !(previousChild instanceof SilentHeader) + && !(child instanceof FooterView); } private void updatePulsingStates(StackScrollAlgorithmState algorithmState, @@ -514,42 +646,6 @@ public class StackScrollAlgorithm { childState.yTranslation = newTranslation; } - /** - * Clamp the height of the child down such that its end is at most on the beginning of - * the shelf. - * - * @param childViewState the view state of the child - * @param ambientState the ambient state - */ - private void clampPositionToShelf(ExpandableView child, - ExpandableViewState childViewState, - AmbientState ambientState) { - if (ambientState.getShelf() == null) { - return; - } - - ExpandableNotificationRow trackedHeadsUpRow = ambientState.getTrackedHeadsUpRow(); - boolean isBeforeTrackedHeadsUp = trackedHeadsUpRow != null - && mHostView.indexOfChild(child) < mHostView.indexOfChild(trackedHeadsUpRow); - - int shelfStart = ambientState.getInnerHeight() - - ambientState.getShelf().getIntrinsicHeight(); - if (ambientState.isAppearing() && !child.isAboveShelf() && !isBeforeTrackedHeadsUp) { - // Don't show none heads-up notifications while in appearing phase. - childViewState.yTranslation = Math.max(childViewState.yTranslation, shelfStart); - } - childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart); - if (child instanceof SectionHeaderView) { - // Add padding before sections for overscroll effect. - childViewState.yTranslation += ambientState.getSectionPadding(); - } - if (childViewState.yTranslation >= shelfStart) { - childViewState.hidden = !child.isExpandAnimationRunning() && !child.hasExpandingChild(); - childViewState.inShelf = true; - childViewState.headsUpIsVisible = false; - } - } - protected int getMaxAllowedChildHeight(View child) { if (child instanceof ExpandableView) { ExpandableView expandableView = (ExpandableView) child; @@ -641,6 +737,35 @@ public class StackScrollAlgorithm { this.mIsExpanded = isExpanded; } + /** + * Data used to layout views while shade expansion changes. + */ + public class ExpansionData { + + /** + * Y position of top of first view in section. + */ + public float sectionStart; + + /** + * Y position of bottom of last view in section. + */ + public float sectionEnd; + + /** + * Y position of view when shade is fully expanded. + * Does not include distance between top notifications panel and top of screen. + */ + public float fullyExpandedY; + + /** + * Whether this notification is in the same section as the notification right before the + * shelf. Used to determine which notification should be clipped to shelf start while + * shade expansion changes. + */ + public boolean inOverflowingSection; + } + public class StackScrollAlgorithmState { /** @@ -649,15 +774,26 @@ public class StackScrollAlgorithm { public int scrollY; /** - * The children from the host view which are not gone. + * First view in shelf. */ - public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>(); + public ExpandableView firstViewInShelf; - private int indexOfExpandingNotification; + /** + * First view in section overflowing into shelf while shade expansion changes. + */ + public ExpandableView firstViewInOverflowSection; - public int getIndexOfExpandingNotification() { - return indexOfExpandingNotification; - } + /** + * Map of view to ExpansionData used for layout during shade expansion. + * Use view instead of index as key, because visibleChildren indices do not match the ones + * used in the shelf. + */ + public Map<ExpandableView, ExpansionData> expansionData = new HashMap<>(); + + /** + * The children from the host view which are not gone. + */ + public final ArrayList<ExpandableView> visibleChildren = new ArrayList<>(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 40e6b40ffa14..4e57e44f38d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -19,10 +19,16 @@ import static android.app.StatusBarManager.DISABLE_CLOCK; import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; + +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Fragment; import android.os.Bundle; import android.os.Parcelable; +import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; @@ -36,6 +42,9 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; @@ -44,6 +53,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; +import org.jetbrains.annotations.NotNull; + import java.util.ArrayList; import java.util.List; @@ -55,7 +66,8 @@ import javax.inject.Inject; * updated by the StatusBarIconController and DarkIconManager while it is attached. */ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks, - StatusBarStateController.StateListener { + StatusBarStateController.StateListener, + SystemStatusAnimationCallback { public static final String TAG = "CollapsedStatusBarFragment"; private static final String EXTRA_PANEL_STATE = "panel_state"; @@ -78,6 +90,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private View mOperatorNameFrame; private CommandQueue mCommandQueue; private OngoingCallController mOngoingCallController; + private final SystemStatusAnimationScheduler mAnimationScheduler; + private final PrivacyDotViewController mDotViewController; private List<String> mBlockedIcons = new ArrayList<>(); @@ -103,8 +117,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue }; @Inject - public CollapsedStatusBarFragment(OngoingCallController ongoingCallController) { + public CollapsedStatusBarFragment( + OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController + ) { mOngoingCallController = ongoingCallController; + mAnimationScheduler = animationScheduler; + mDotViewController = dotViewController; } @Override @@ -127,6 +147,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mStatusBar = (PhoneStatusBarView) view; + View contents = mStatusBar.findViewById(R.id.status_bar_contents); + contents.addOnLayoutChangeListener(mStatusBarLayoutListener); + updateStatusBarLocation(contents.getLeft(), contents.getRight()); if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) { mStatusBar.restoreHierarchyState( savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE)); @@ -143,6 +166,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue showClock(false); initEmergencyCryptkeeperText(); initOperatorName(); + mAnimationScheduler.addCallback(this); } @Override @@ -208,6 +232,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue if (displayId != getContext().getDisplayId()) { return; } + Log.d(TAG, "disable: "); state1 = adjustDisableFlags(state1); final int old1 = mDisabled1; final int diff1 = state1 ^ old1; @@ -292,19 +317,22 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return false; } - public void hideSystemIconArea(boolean animate) { + private void hideSystemIconArea(boolean animate) { animateHide(mSystemIconArea, animate); } - public void showSystemIconArea(boolean animate) { - animateShow(mSystemIconArea, animate); + private void showSystemIconArea(boolean animate) { + // Only show the system icon area if we are not currently animating + if (mAnimationScheduler.getAnimationState() == IDLE) { + animateShow(mSystemIconArea, animate); + } } - public void hideClock(boolean animate) { + private void hideClock(boolean animate) { animateHiddenState(mClockView, clockHiddenMode(), animate); } - public void showClock(boolean animate) { + private void showClock(boolean animate) { animateShow(mClockView, animate); } @@ -425,12 +453,60 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } @Override - public void onStateChanged(int newState) { - - } + public void onStateChanged(int newState) { } @Override public void onDozingChanged(boolean isDozing) { disable(getContext().getDisplayId(), mDisabled1, mDisabled1, false /* animate */); } + + @Override + public void onSystemChromeAnimationStart() { + if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT + && !isSystemIconAreaDisabled()) { + mSystemIconArea.setVisibility(View.VISIBLE); + mSystemIconArea.setAlpha(0f); + } + } + + @Override + public void onSystemChromeAnimationEnd() { + // Make sure the system icons are out of the way + if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) { + mSystemIconArea.setVisibility(View.INVISIBLE); + mSystemIconArea.setAlpha(0f); + } else { + if (isSystemIconAreaDisabled()) { + // don't unhide + return; + } + + mSystemIconArea.setAlpha(1f); + mSystemIconArea.setVisibility(View.VISIBLE); + } + } + + @Override + public void onSystemChromeAnimationUpdate(@NotNull ValueAnimator animator) { + mSystemIconArea.setAlpha((float) animator.getAnimatedValue()); + } + + private boolean isSystemIconAreaDisabled() { + return (mDisabled1 & DISABLE_SYSTEM_INFO) != 0 || (mDisabled2 & DISABLE2_SYSTEM_ICONS) != 0; + } + + private void updateStatusBarLocation(int left, int right) { + int leftMargin = left - mStatusBar.getLeft(); + int rightMargin = mStatusBar.getRight() - right; + + mDotViewController.setStatusBarMargins(leftMargin, rightMargin); + } + + // Listen for view end changes of PhoneStatusBarView and publish that to the privacy dot + private View.OnLayoutChangeListener mStatusBarLayoutListener = + (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { + if (left != oldLeft || right != oldRight) { + updateStatusBarLocation(left, right); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index c3325b103022..b83039b9f7f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -68,6 +68,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -154,6 +155,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private StatusBar mStatusBar; private KeyguardAffordanceHelper mAffordanceHelper; private FalsingManager mFalsingManager; + @Nullable private Executor mUiExecutor; private boolean mUserSetupComplete; private boolean mPrewarmBound; private Messenger mPrewarmMessenger; @@ -429,7 +431,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } private void updateWalletVisibility() { - if (mDozing || !mWalletEnabled) { + if (mDozing || !mWalletEnabled || !mHasCard) { mWalletButton.setVisibility(GONE); } else { mWalletButton.setVisibility(VISIBLE); @@ -659,6 +661,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL updateCameraVisibility(); } + @Override + public void onKeyguardShowingChanged() { + if (mKeyguardStateController.isShowing()) { + queryWalletCards(); + } + } + private void inflateCameraPreview() { View previewBefore = mCameraPreview; boolean visibleBefore = false; @@ -897,18 +906,20 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void initWallet(QuickAccessWalletClient client, Executor uiExecutor, boolean enabled) { mQuickAccessWalletClient = client; mWalletEnabled = enabled && client.isWalletFeatureAvailable(); + mUiExecutor = uiExecutor; + queryWalletCards(); - if (mWalletEnabled) { - queryWalletCards(uiExecutor); - } updateWalletVisibility(); } - private void queryWalletCards(Executor uiExecutor) { + private void queryWalletCards() { + if (!mWalletEnabled || mUiExecutor == null) { + return; + } GetWalletCardsRequest request = new GetWalletCardsRequest(1 /* cardWidth */, 1 /* cardHeight */, 1 /* iconSizePx */, 2 /* maxCards */); - mQuickAccessWalletClient.getWalletCards(uiExecutor, request, mCardRetriever); + mQuickAccessWalletClient.getWalletCards(mUiExecutor, request, mCardRetriever); } private void onWalletClick(View v) { 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/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 06947376b9f1..c22fec97db01 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -18,7 +18,10 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.ScreenDecorations.DisplayCutoutView.boundsFromDirection; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; +import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; +import android.animation.ValueAnimator; import android.annotation.ColorInt; import android.content.Context; import android.content.res.Configuration; @@ -47,6 +50,8 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -64,8 +69,11 @@ import java.util.List; /** * The header group on Keyguard. */ -public class KeyguardStatusBarView extends RelativeLayout - implements BatteryStateChangeCallback, OnUserInfoChangedListener, ConfigurationListener { +public class KeyguardStatusBarView extends RelativeLayout implements + BatteryStateChangeCallback, + OnUserInfoChangedListener, + ConfigurationListener, + SystemStatusAnimationCallback { private static final int LAYOUT_NONE = 0; private static final int LAYOUT_CUTOUT = 1; @@ -96,6 +104,8 @@ public class KeyguardStatusBarView extends RelativeLayout private ViewGroup mStatusIconArea; private int mLayoutState = LAYOUT_NONE; + private SystemStatusAnimationScheduler mAnimationScheduler; + /** * Draw this many pixels into the left/right side of the cutout to optimally use the space */ @@ -125,6 +135,7 @@ public class KeyguardStatusBarView extends RelativeLayout loadDimens(); loadBlockList(); mBatteryController = Dependency.get(BatteryController.class); + mAnimationScheduler = Dependency.get(SystemStatusAnimationScheduler.class); } @Override @@ -349,6 +360,7 @@ public class KeyguardStatusBarView extends RelativeLayout mIconManager = new TintedIconManager(findViewById(R.id.statusIcons)); mIconManager.setBlockList(mBlockedIcons); Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); + mAnimationScheduler.addCallback(this); onThemeChanged(); } @@ -358,6 +370,7 @@ public class KeyguardStatusBarView extends RelativeLayout Dependency.get(UserInfoController.class).removeCallback(this); Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); Dependency.get(ConfigurationController.class).removeCallback(this); + mAnimationScheduler.removeCallback(this); } @Override @@ -509,4 +522,30 @@ public class KeyguardStatusBarView extends RelativeLayout mBatteryView.dump(fd, pw, args); } } + + /** SystemStatusAnimationCallback */ + @Override + public void onSystemChromeAnimationStart() { + if (mAnimationScheduler.getAnimationState() == ANIMATING_OUT) { + mSystemIconsContainer.setVisibility(View.VISIBLE); + mSystemIconsContainer.setAlpha(0f); + } + } + + @Override + public void onSystemChromeAnimationEnd() { + // Make sure the system icons are out of the way + if (mAnimationScheduler.getAnimationState() == ANIMATING_IN) { + mSystemIconsContainer.setVisibility(View.INVISIBLE); + mSystemIconsContainer.setAlpha(0f); + } else { + mSystemIconsContainer.setAlpha(1f); + mSystemIconsContainer.setVisibility(View.VISIBLE); + } + } + + @Override + public void onSystemChromeAnimationUpdate(ValueAnimator anim) { + mSystemIconsContainer.setAlpha((float) anim.getAnimatedValue()); + } } 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..c4d884071a78 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 = mNotificationStackScrollLayoutController.getHeight(); + left = mNotificationStackScrollLayoutController.getLeft(); + right = mNotificationStackScrollLayoutController.getRight(); + } + } + mScrimController.setNotificationsBounds(left, top, right, bottom); + } + private int calculateQsBottomPosition(float qsExpansionFraction) { int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight(); if (qsExpansionFraction != 0.0) { @@ -2030,7 +2053,7 @@ 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/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index d386ebde26bf..43d525d5098e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -208,6 +208,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider; @@ -426,6 +428,8 @@ public class StatusBar extends SystemUI implements DemoMode, private final DemoModeController mDemoModeController; private NotificationsController mNotificationsController; private final OngoingCallController mOngoingCallController; + private final SystemStatusAnimationScheduler mAnimationScheduler; + private final PrivacyDotViewController mDotViewController; // expanded notifications // the sliding/resizing panel within the notification window @@ -794,6 +798,8 @@ public class StatusBar extends SystemUI implements DemoMode, BrightnessSlider.Factory brightnessSliderFactory, WiredChargingRippleController chargingRippleAnimationController, OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController, TunerService tunerService, FeatureFlags featureFlags) { super(context); @@ -875,6 +881,8 @@ public class StatusBar extends SystemUI implements DemoMode, mBrightnessSliderFactory = brightnessSliderFactory; mChargingRippleAnimationController = chargingRippleAnimationController; mOngoingCallController = ongoingCallController; + mAnimationScheduler = animationScheduler; + mDotViewController = dotViewController; mFeatureFlags = featureFlags; tunerService.addTunable( @@ -1171,7 +1179,10 @@ public class StatusBar extends SystemUI implements DemoMode, }).getFragmentManager() .beginTransaction() .replace(R.id.status_bar_container, - new CollapsedStatusBarFragment(mOngoingCallController), + new CollapsedStatusBarFragment( + mOngoingCallController, + mAnimationScheduler, + mDotViewController), CollapsedStatusBarFragment.TAG) .commit(); @@ -1788,7 +1799,15 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void startActivity(Intent intent, boolean dismissShade) { - startActivityDismissingKeyguard(intent, false, dismissShade); + startActivityDismissingKeyguard(intent, false /* onlyProvisioned */, dismissShade); + } + + @Override + public void startActivity(Intent intent, boolean dismissShade, + ActivityLaunchAnimator.Controller animationController) { + startActivityDismissingKeyguard(intent, false, dismissShade, + false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */, + 0 /* flags */, animationController); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 2c2779e53e16..24e6db818ef6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -61,6 +61,8 @@ import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; @@ -209,6 +211,8 @@ public interface StatusBarPhoneModule { BrightnessSlider.Factory brightnessSliderFactory, WiredChargingRippleController chargingRippleAnimationController, OngoingCallController ongoingCallController, + SystemStatusAnimationScheduler animationScheduler, + PrivacyDotViewController dotViewController, TunerService tunerService, FeatureFlags featureFlags) { return new StatusBar( @@ -293,6 +297,8 @@ public interface StatusBarPhoneModule { brightnessSliderFactory, chargingRippleAnimationController, ongoingCallController, + animationScheduler, + dotViewController, tunerService, featureFlags); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 55b80dd69b6a..db7736620c26 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -633,7 +633,7 @@ public class BubblesManager implements Dumpable { } else { mNotificationGroupManager.onEntryRemoved(entry); } - }); + }, mSysuiMainExecutor); } /** 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/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 59262cf3231b..325275047ebb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -63,6 +63,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R.dimen; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; @@ -94,6 +95,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserTracker mUserTracker; + @Mock + private PrivacyDotViewController mDotViewController; @Before public void setup() { @@ -116,7 +119,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { mContext.addMockSystemService(DisplayManager.class, mDisplayManager); mScreenDecorations = spy(new ScreenDecorations(mContext, mMainHandler, mSecureSettings, - mBroadcastDispatcher, mTunerService, mUserTracker) { + mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController) { @Override public void start() { super.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index 3f0831cadd1f..78c67170d185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -233,9 +233,9 @@ public class AppOpsControllerTest extends SysuiTestCase { TEST_UID, TEST_PACKAGE_NAME, TEST_ATTRIBUTION_NAME, AppOpsManager.OP_FLAG_SELF, AppOpsManager.MODE_ALLOWED); assertEquals(2, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID)).size()); - assertEquals(1, - mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size()); + mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID), false).size()); + assertEquals(1, mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER), + false).size()); } @Test @@ -245,11 +245,11 @@ public class AppOpsControllerTest extends SysuiTestCase { mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, mExemptedRolePkgName, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, TEST_UID_NON_USER_SENSITIVE, SYSTEM_PKG, true); assertEquals(0, mController.getActiveAppOpsForUser( - UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE), false).size()); } @Test @@ -441,7 +441,19 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test - public void testOnlyRecordAudioPaused() { + public void testPausedPhoneCallMicrophoneFilteredOut() { + mController.addCallback(new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE}, mCallback); + mTestableLooper.processAllMessages(); + + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + + assertTrue(mController.getActiveAppOps().isEmpty()); + } + + @Test + public void testOnlyRecordAudioPhoneCallMicrophonePaused() { mController.addCallback(new int[]{ AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA @@ -532,6 +544,40 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test + public void testPhoneCallMicrophoneFilteredWhenMicDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_PHONE_CALL_MICROPHONE, AppOpsManager.OP_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add a camera op, and disable the microphone. The camera op should be the only op returned + mController.onSensorBlockedChanged(MICROPHONE, true); + mController.onOpActiveChanged( + AppOpsManager.OP_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_CAMERA, list.get(0).getCode()); + + + // Re enable the microphone, and verify the op returns + mController.onSensorBlockedChanged(MICROPHONE, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int micIdx = list.get(0).getCode() == AppOpsManager.OP_CAMERA ? 1 : 0; + assertEquals(AppOpsManager.OP_PHONE_CALL_MICROPHONE, list.get(micIdx).getCode()); + } + + @Test public void testCameraFilteredWhenCameraDisabled() { mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_CAMERA}, mCallback); @@ -563,6 +609,39 @@ public class AppOpsControllerTest extends SysuiTestCase { assertEquals(AppOpsManager.OP_CAMERA, list.get(cameraIdx).getCode()); } + @Test + public void testPhoneCallCameraFilteredWhenCameraDisabled() { + mController.addCallback( + new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_PHONE_CALL_CAMERA}, + mCallback); + mTestableLooper.processAllMessages(); + mController.onOpActiveChanged( + AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + List<AppOpItem> list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(0).getCode()); + assertFalse(list.get(0).isDisabled()); + + // Add an audio op, and disable the camera. The audio op should be the only op returned + mController.onSensorBlockedChanged(CAMERA, true); + mController.onOpActiveChanged( + AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true); + mTestableLooper.processAllMessages(); + list = mController.getActiveAppOps(); + assertEquals(1, list.size()); + assertEquals(AppOpsManager.OP_RECORD_AUDIO, list.get(0).getCode()); + + // Re enable the camera, and verify the op returns + mController.onSensorBlockedChanged(CAMERA, false); + mTestableLooper.processAllMessages(); + + list = mController.getActiveAppOps(); + assertEquals(2, list.size()); + int cameraIdx = list.get(0).getCode() == AppOpsManager.OP_PHONE_CALL_CAMERA ? 0 : 1; + assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode()); + } + private class TestHandler extends AppOpsControllerImpl.H { TestHandler(Looper looper) { mController.super(looper); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 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/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java index 107ac831811f..3cc55f2f9070 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java @@ -114,8 +114,6 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { when(mMockContext.getString(R.string.birthday_status)).thenReturn( mContext.getString(R.string.birthday_status)); - when(mMockContext.getString(R.string.basic_status)).thenReturn( - mContext.getString(R.string.basic_status)); when(mMockContext.getPackageManager()).thenReturn(mPackageManager); when(mMockContext.getString(R.string.over_timestamp)).thenReturn( mContext.getString(R.string.over_timestamp)); @@ -126,7 +124,6 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { when(resources.getConfiguration()).thenReturn(configuration); when(resources.getDisplayMetrics()).thenReturn(displayMetrics); TextView textView = mock(TextView.class); - // when(new TextView(mMockContext)).thenReturn(textView); when(textView.getLineHeight()).thenReturn(16); when(mPackageManager.getApplicationIcon(anyString())).thenReturn(null); mPeopleTileViewHelper = new PeopleTileViewHelper(mContext, @@ -134,16 +131,41 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { } @Test - public void testCreateRemoteViewsWithLastInteractionTime() { + public void testCreateRemoteViewsWithLastInteractionTimeUnderOneDayHidden() { RemoteViews views = new PeopleTileViewHelper(mContext, PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); View result = views.apply(mContext, null); + // Not showing last interaction. + assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility()); + + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_width_for_large)); + mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, + getSizeInDp(R.dimen.required_height_for_large)); + RemoteViews largeView = new PeopleTileViewHelper(mContext, + PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + View largeResult = largeView.apply(mContext, null); + + // Not showing last interaction. + assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility()); + } + + @Test + public void testCreateRemoteViewsWithLastInteractionTime() { + PeopleSpaceTile tileWithLastInteraction = + PERSON_TILE_WITHOUT_NOTIFICATION.toBuilder().setLastInteractionTimestamp( + 123445L).build(); + RemoteViews views = new PeopleTileViewHelper(mContext, + tileWithLastInteraction, 0, mOptions).getViews(); + View result = views.apply(mContext, null); + TextView name = (TextView) result.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. + assertEquals(View.VISIBLE, result.findViewById(R.id.last_interaction).getVisibility()); TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(lastInteraction.getText(), "Over 2 weeks ago"); // No availability. assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility()); // Shows person icon. @@ -154,7 +176,7 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_width_for_medium) - 1); RemoteViews smallView = new PeopleTileViewHelper(mContext, - PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + tileWithLastInteraction, 0, mOptions).getViews(); View smallResult = smallView.apply(mContext, null); // Show name over predefined icon. @@ -171,14 +193,15 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { mOptions.putInt(OPTION_APPWIDGET_MIN_WIDTH, getSizeInDp(R.dimen.required_height_for_large)); RemoteViews largeView = new PeopleTileViewHelper(mContext, - PERSON_TILE_WITHOUT_NOTIFICATION, 0, mOptions).getViews(); + tileWithLastInteraction, 0, mOptions).getViews(); View largeResult = largeView.apply(mContext, null); name = (TextView) largeResult.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.last_interaction).getVisibility()); lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(lastInteraction.getText(), "Over 2 weeks ago"); // No availability. assertEquals(View.GONE, result.findViewById(R.id.availability).getVisibility()); // Shows person icon. @@ -202,8 +225,7 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { TextView name = (TextView) result.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction over status. - TextView lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(View.GONE, result.findViewById(R.id.last_interaction).getVisibility()); // Has availability. assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); // Has person icon. @@ -237,14 +259,13 @@ public class PeopleTileViewHelperTest extends SysuiTestCase { name = (TextView) largeResult.findViewById(R.id.name); assertEquals(name.getText(), NAME); // Has last interaction. - lastInteraction = (TextView) result.findViewById(R.id.last_interaction); - assertEquals(lastInteraction.getText(), mContext.getString(R.string.basic_status)); + assertEquals(View.GONE, largeResult.findViewById(R.id.last_interaction).getVisibility()); // Has availability. - assertEquals(View.VISIBLE, result.findViewById(R.id.availability).getVisibility()); + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.availability).getVisibility()); // Shows person icon. - assertEquals(View.VISIBLE, result.findViewById(R.id.person_icon).getVisibility()); + assertEquals(View.VISIBLE, largeResult.findViewById(R.id.person_icon).getVisibility()); // No status. - assertThat((View) result.findViewById(R.id.text_content)).isNull(); + assertThat((View) largeResult.findViewById(R.id.text_content)).isNull(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 791dd121852f..05a1e4ff474d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -28,6 +28,7 @@ import android.permission.PermissionManager import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.appops.AppOpsController import com.android.systemui.plugins.ActivityStarter import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.settings.UserTracker @@ -43,6 +44,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Captor @@ -86,6 +88,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { private lateinit var privacyLogger: PrivacyLogger @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var appOpsController: AppOpsController @Captor private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialog.OnDialogDismissed> @Captor @@ -131,6 +135,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { uiExecutor, privacyLogger, keyguardStateController, + appOpsController, dialogProvider ) } @@ -143,18 +148,27 @@ class PrivacyDialogControllerTest : SysuiTestCase() { } @Test + fun testMicMutedParameter() { + `when`(appOpsController.isMicMuted).thenReturn(true) + controller.showDialog(context) + backgroundExecutor.runAllReady() + + verify(permissionManager).getIndicatorAppOpUsageData(true) + } + + @Test fun testPermissionManagerOnlyCalledInBackgroundThread() { controller.showDialog(context) - verify(permissionManager, never()).indicatorAppOpUsageData + verify(permissionManager, never()).getIndicatorAppOpUsageData(anyBoolean()) backgroundExecutor.runAllReady() - verify(permissionManager).indicatorAppOpUsageData + verify(permissionManager).getIndicatorAppOpUsageData(anyBoolean()) } @Test fun testPackageManagerOnlyCalledInBackgroundThread() { val usage = createMockPermGroupUsage() `when`(usage.isPhoneCall).thenReturn(false) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt()) @@ -217,7 +231,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isPhoneCall = false, attribution = TEST_ATTRIBUTION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage)) controller.showDialog(context) exhaustExecutors() @@ -246,7 +260,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_microphone", permGroupName = PERM_MICROPHONE ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_microphone, usage_camera) ) @@ -269,7 +283,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_active) ) @@ -292,7 +306,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = true, lastAccess = 1L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_active, usage_active_moreRecent) ) controller.showDialog(context) @@ -319,7 +333,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { isActive = false, lastAccess = 2L ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_recent, usage_mostRecent, usage_moreRecent) ) @@ -342,7 +356,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -366,7 +380,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.locationAvailable).thenReturn(false) @@ -392,7 +406,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(true) @@ -416,7 +430,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() { permGroupName = PERM_LOCATION ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn( + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn( listOf(usage_camera, usage_location, usage_microphone) ) `when`(privacyItemController.micCameraAvailable).thenReturn(false) @@ -433,7 +447,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_enterprise = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_enterprise)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_enterprise)) controller.showDialog(context) exhaustExecutors() @@ -446,7 +461,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { val usage_other = createMockPermGroupUsage( uid = generateUidForUser(ENT_USER_ID + 1) ) - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(listOf(usage_other)) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())) + .thenReturn(listOf(usage_other)) controller.showDialog(context) exhaustExecutors() @@ -514,7 +530,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() { } private fun setUpDefaultMockResponses() { - `when`(permissionManager.indicatorAppOpUsageData).thenReturn(emptyList()) + `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(emptyList()) + `when`(appOpsController.isMicMuted).thenReturn(false) `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) .thenAnswer { diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index bba1c6a00675..e4d7b1b7d451 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -46,7 +46,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyList import org.mockito.Captor import org.mockito.Mock @@ -156,7 +156,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testDistinctItems() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -168,7 +168,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testSimilarItemsDifferentTimeStamp() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -215,7 +215,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testMultipleCallbacksAreUpdated() { - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) @@ -233,7 +233,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testRemoveCallback() { - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) privacyItemController.addCallback(otherCallback) @@ -254,7 +254,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testListShouldNotHaveNull() { doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() executor.runAllReady() @@ -292,7 +292,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -306,7 +306,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { @Test fun testNotUpdated_LocationChangeWhenOnlyMicCamera() { doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) - .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + .`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) changeLocation(false) @@ -338,7 +338,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testLogListUpdated() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() @@ -362,10 +362,10 @@ class PrivacyItemControllerTest : SysuiTestCase() { } @Test - fun testListRequestedForAllUsers() { + fun testListRequestedShowPaused() { privacyItemController.addCallback(callback) executor.runAllReady() - verify(appOpsController).getActiveAppOpsForUser(UserHandle.USER_ALL) + verify(appOpsController).getActiveAppOps(true) } @Test @@ -377,7 +377,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { doReturn(listOf( AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, otherUserUid, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -401,7 +401,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -424,7 +424,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0), AppOpItem(AppOpsManager.OP_PHONE_CALL_MICROPHONE, TEST_UID, TEST_PACKAGE_NAME, 0)) - ).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + ).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.userTrackerCallback.onUserChanged(otherUser, mContext) executor.runAllReady() @@ -442,7 +442,7 @@ class PrivacyItemControllerTest : SysuiTestCase() { fun testPassageOfTimeDoesNotRemoveIndicators() { doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) @@ -458,12 +458,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD + 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -481,12 +481,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -504,12 +504,12 @@ class PrivacyItemControllerTest : SysuiTestCase() { // Start with some element at time 0 doReturn(listOf( AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime) - )).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + )).`when`(appOpsController).getActiveAppOps(anyBoolean()) privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 - doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOps(anyBoolean()) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) @@ -525,6 +525,30 @@ class PrivacyItemControllerTest : SysuiTestCase() { assertTrue(privacyItemController.privacyList.isEmpty()) } + @Test + fun testPausedElementsAreRemoved() { + val item = AppOpItem( + AppOpsManager.OP_RECORD_AUDIO, + TEST_UID, + TEST_PACKAGE_NAME, + elapsedTime + ) + `when`(appOpsController.getActiveAppOps(anyBoolean())).thenReturn(listOf(item)) + privacyItemController.addCallback(callback) + executor.runAllReady() + + item.isDisabled = true + fakeClock.advanceTime(1) + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged( + AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) + + executor.runAllReady() + + verify(callback).onPrivacyItemsChanged(emptyList()) + assertTrue(privacyItemController.privacyList.isEmpty()) + } + private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeLocation(value: Boolean?) = changeProperty(LOCATION_INDICATOR, value) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java index 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/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index 67fd5eb1acac..929a7e1543fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -37,6 +37,8 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; @@ -58,6 +60,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private View mCenteredNotificationAreaView; private StatusBarStateController mStatusBarStateController; private OngoingCallController mOngoingCallController; + private SystemStatusAnimationScheduler mAnimationScheduler; + private PrivacyDotViewController mDotViewController; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -212,6 +216,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { mOngoingCallController = mock(OngoingCallController.class); - return new CollapsedStatusBarFragment(mOngoingCallController); + mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); + mDotViewController = mock(PrivacyDotViewController.class); + return new CollapsedStatusBarFragment( + mOngoingCallController, + mAnimationScheduler, + mDotViewController); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index 08d6d2d7c72d..3989dfa55991 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -113,6 +113,8 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.charging.WiredChargingRippleController; +import com.android.systemui.statusbar.events.PrivacyDotViewController; +import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.DynamicPrivacyController; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -263,6 +265,8 @@ public class StatusBarTest extends SysuiTestCase { @Mock private BrightnessSlider.Factory mBrightnessSliderFactory; @Mock private WiredChargingRippleController mWiredChargingRippleController; @Mock private OngoingCallController mOngoingCallController; + @Mock private SystemStatusAnimationScheduler mAnimationScheduler; + @Mock private PrivacyDotViewController mDotViewController; @Mock private TunerService mTunerService; @Mock private FeatureFlags mFeatureFlags; private ShadeController mShadeController; @@ -429,6 +433,8 @@ public class StatusBarTest extends SysuiTestCase { mBrightnessSliderFactory, mWiredChargingRippleController, mOngoingCallController, + mAnimationScheduler, + mDotViewController, mTunerService, mFeatureFlags); when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 4471778a03ab..40439a2bebb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -36,7 +36,6 @@ import static org.mockito.Mockito.when; import android.app.Instrumentation; import android.net.ConnectivityManager; -import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; @@ -323,34 +322,37 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaCallbackInNetworkControllerForVcn( int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) { - mNetCapabilities.setTransportInfo(info); - setConnectivityCommon(networkType, validated, isConnected); - mDefaultCallbackInNetworkController.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); + builder.setTransportInfo(info); + setConnectivityCommon(builder, networkType, validated, isConnected); + mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build()); } public void setConnectivityViaCallbackInNetworkController( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); - mDefaultCallbackInNetworkController.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + setConnectivityCommon(builder, networkType, validated, isConnected); + mDefaultCallbackInNetworkController.onCapabilitiesChanged(mNetwork, builder.build()); } public void setConnectivityViaCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); + setConnectivityCommon(builder, networkType, validated, isConnected); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { if (isConnected) { - mNetworkCallback.onAvailable(mNetwork, - new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false); - mNetworkCallback.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities newCap = builder.build(); + mNetworkCallback.onAvailable(mNetwork); + mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap); } else { mNetworkCallback.onLost(mNetwork); } @@ -359,16 +361,16 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaCallbackInWifiTrackerForVcn( int networkType, boolean validated, boolean isConnected, VcnTransportInfo info) { - mNetCapabilities.setTransportInfo(info); - setConnectivityCommon(networkType, validated, isConnected); + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); + builder.setTransportInfo(info); + setConnectivityCommon(builder, networkType, validated, isConnected); if (networkType == NetworkCapabilities.TRANSPORT_CELLULAR) { if (isConnected) { - mNetworkCallback.onAvailable(mNetwork, - new NetworkCapabilities(mNetCapabilities), new LinkProperties(), false); - mNetworkCallback.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); - mDefaultCallbackInWifiTracker.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + final NetworkCapabilities newCap = builder.build(); + mNetworkCallback.onAvailable(mNetwork); + mNetworkCallback.onCapabilitiesChanged(mNetwork, newCap); + mDefaultCallbackInWifiTracker.onCapabilitiesChanged(mNetwork, newCap); } else { mNetworkCallback.onLost(mNetwork); } @@ -377,26 +379,28 @@ public class NetworkControllerBaseTest extends SysuiTestCase { public void setConnectivityViaDefaultCallbackInWifiTracker( int networkType, boolean validated, boolean isConnected, WifiInfo wifiInfo) { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder(mNetCapabilities); if (networkType == NetworkCapabilities.TRANSPORT_WIFI) { - mNetCapabilities.setTransportInfo(wifiInfo); + builder.setTransportInfo(wifiInfo); } - setConnectivityCommon(networkType, validated, isConnected); + setConnectivityCommon(builder, networkType, validated, isConnected); mDefaultCallbackInWifiTracker.onCapabilitiesChanged( - mNetwork, new NetworkCapabilities(mNetCapabilities)); + mNetwork, builder.build()); } - private void setConnectivityCommon( + private static void setConnectivityCommon(NetworkCapabilities.Builder builder, int networkType, boolean validated, boolean isConnected){ // TODO: Separate out into several NetworkCapabilities. if (isConnected) { - mNetCapabilities.addTransportType(networkType); + builder.addTransportType(networkType); } else { - mNetCapabilities.removeTransportType(networkType); + builder.removeTransportType(networkType); } if (validated) { - mNetCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + builder.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } else { - mNetCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java index ed87a4040022..c38a54771e12 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java @@ -202,8 +202,8 @@ public class SecurityControllerTest extends SysuiTestCase { @Test public void testNetworkRequest() { verify(mConnectivityManager, times(1)).registerNetworkCallback(argThat( - (NetworkRequest request) -> request.networkCapabilities.getUids() == null - && request.networkCapabilities.getCapabilities().length == 0 + (NetworkRequest request) -> + request.equals(new NetworkRequest.Builder().clearCapabilities().build()) ), any(NetworkCallback.class)); } 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..4650eae59a6a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -630,7 +630,9 @@ public class ConnectivityService extends IConnectivityManager.Stub } private static IDnsResolver getDnsResolver(Context context) { - return IDnsResolver.Stub.asInterface(DnsResolverServiceManager.getService(context)); + final DnsResolverServiceManager dsm = context.getSystemService( + DnsResolverServiceManager.class); + return IDnsResolver.Stub.asInterface(dsm.getService()); } /** Handler thread used for all of the handlers below. */ @@ -1353,8 +1355,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 +3169,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; } } } @@ -6143,6 +6150,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public int registerNetworkProvider(Messenger messenger, String name) { enforceNetworkFactoryOrSettingsPermission(); + Objects.requireNonNull(messenger, "messenger must be non-null"); NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, nextNetworkProviderId(), () -> unregisterNetworkProvider(messenger)); mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); @@ -6516,7 +6524,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 +7768,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"); } @@ -9180,6 +9189,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void unregisterConnectivityDiagnosticsCallback( @NonNull IConnectivityDiagnosticsCallback callback) { + Objects.requireNonNull(callback, "callback must be non-null"); mConnectivityDiagnosticsHandler.sendMessage( mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler @@ -9550,6 +9560,7 @@ public class ConnectivityService extends IConnectivityManager.Stub */ @Override public void unregisterQosCallback(@NonNull final IQosCallback callback) { + Objects.requireNonNull(callback, "callback must be non-null"); mQosCallbackTracker.unregisterCallback(callback); } diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 9f91dd6b982b..483250ad2257 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -174,9 +174,9 @@ option java_package com.android.server # Disk usage stats for verifying quota correctness 3121 pm_package_stats (manual_time|2|3),(quota_time|2|3),(manual_data|2|2),(quota_data|2|2),(manual_cache|2|2),(quota_cache|2|2) # Snapshot statistics -3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(quick_rebuilds|1|1),(max_build_time|1|3),(cumm_build_time|1|3) +3130 pm_snapshot_stats (build_count|1|1),(reuse_count|1|1),(big_builds|1|1),(short_lived|1|1),(max_build_time|1|3),(cumm_build_time|2|3) # Snapshot rebuild instance -3131 pm_snapshot_rebuild (build_time|1|3),(elapsed|1|3) +3131 pm_snapshot_rebuild (build_time|1|3),(lifetime|1|3) # --------------------------- # InputMethodManagerService.java diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index 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..2d486c47d3c8 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); } @@ -2550,11 +2551,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { * Send a notification to registrants that the configs of physical channel has changed for * a particular subscription. * + * @param phoneId the phone id. * @param subId the subId * @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel. */ - public void notifyPhysicalChannelConfigForSubscriber( - int subId, List<PhysicalChannelConfig> configs) { + public void notifyPhysicalChannelConfigForSubscriber(int phoneId, int subId, + List<PhysicalChannelConfig> configs) { if (!checkNotifyPermission("notifyPhysicalChannelConfig()")) { return; } @@ -2566,9 +2568,8 @@ 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/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index b103defbbe21..b7ef10a5d45d 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -199,13 +199,12 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onPropertiesChanged(Properties properties) { synchronized (mDeviceConfigLock) { - for (String key : properties.getKeyset()) { + for (final String packageName : properties.getKeyset()) { try { // Check if the package is installed before caching it. - final String packageName = keyToPackageName(key); mPackageManager.getPackageInfo(packageName, 0); final GamePackageConfiguration config = - GamePackageConfiguration.fromProperties(key, properties); + GamePackageConfiguration.fromProperties(packageName, properties); if (config.isValid()) { putConfig(config); } else { @@ -290,8 +289,8 @@ public final class GameManagerService extends IGameManagerService.Stub { private final String mPackageName; private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs; - private GamePackageConfiguration(String keyName) { - mPackageName = keyToPackageName(keyName); + private GamePackageConfiguration(String packageName) { + mPackageName = packageName; mModeConfigs = new ArrayMap<>(); } @@ -563,9 +562,9 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - private void loadDeviceConfigLocked() { + void loadDeviceConfigLocked() { final List<PackageInfo> packages = mPackageManager.getInstalledPackages(0); - final String[] packageNames = packages.stream().map(e -> packageNameToKey(e.packageName)) + final String[] packageNames = packages.stream().map(e -> e.packageName) .toArray(String[]::new); synchronized (mDeviceConfigLock) { final Properties properties = DeviceConfig.getProperties( @@ -680,8 +679,7 @@ public final class GameManagerService extends IGameManagerService.Stub { case ACTION_PACKAGE_CHANGED: synchronized (mDeviceConfigLock) { Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_GAME_OVERLAY, - packageNameToKey(packageName)); + DeviceConfig.NAMESPACE_GAME_OVERLAY, packageName); for (String key : properties.getKeyset()) { GamePackageConfiguration config = GamePackageConfiguration.fromProperties(key, @@ -692,7 +690,9 @@ public final class GameManagerService extends IGameManagerService.Stub { break; case ACTION_PACKAGE_REMOVED: disableCompatScale(packageName); - mConfigs.remove(packageName); + synchronized (mDeviceConfigLock) { + mConfigs.remove(packageName); + } break; default: // do nothing @@ -710,23 +710,6 @@ public final class GameManagerService extends IGameManagerService.Stub { mDeviceConfigListener = new DeviceConfigListener(); } - /** - * Valid package name characters are [a-zA-Z0-9_] with a '.' delimiter. Policy keys can only use - * [a-zA-Z0-9_] so we must handle periods. We do this by appending a '_' to any existing - * sequence of '_', then we replace all '.' chars with '_'; - */ - private static String packageNameToKey(String name) { - return name.replaceAll("(_+)", "_$1").replaceAll("\\.", "_"); - } - - /** - * Replace the last '_' in a sequence with '.' (this can be one or more chars), then replace the - * resulting special case '_.' with just '_' to get the original package name. - */ - private static String keyToPackageName(String key) { - return key.replaceAll("(_)(?!\\1)", ".").replaceAll("_\\.", "_"); - } - private String dumpDeviceConfigs() { StringBuilder out = new StringBuilder(); for (String key : mConfigs.keySet()) { diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 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/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java index d56fd1221aa9..b795b549fd61 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java @@ -375,12 +375,12 @@ public class Sensor { } @Override - public void onFeaturesRetrieved(byte[] features, int enrollmentId) { + public void onFeaturesRetrieved(byte[] features) { } @Override - public void onFeatureSet(int enrollmentId, byte feature) { + public void onFeatureSet(byte feature) { } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java index c63af7e4b60c..f1bfd53a5753 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/TestHal.java @@ -32,6 +32,7 @@ import android.util.Slog; */ public class TestHal extends IFace.Stub { private static final String TAG = "face.aidl.TestHal"; + @Override public SensorProps[] getSensorProps() { Slog.w(TAG, "getSensorProps"); @@ -102,16 +103,16 @@ public class TestHal extends IFace.Stub { } @Override - public void getFeatures(int enrollmentId) throws RemoteException { + public void getFeatures() throws RemoteException { Slog.w(TAG, "getFeatures"); - cb.onFeaturesRetrieved(new byte[0], enrollmentId); + cb.onFeaturesRetrieved(new byte[0]); } @Override - public void setFeature(HardwareAuthToken hat, int enrollmentId, - byte feature, boolean enabled) throws RemoteException { + public void setFeature(HardwareAuthToken hat, byte feature, boolean enabled) + throws RemoteException { Slog.w(TAG, "setFeature"); - cb.onFeatureSet(enrollmentId, feature); + cb.onFeatureSet(feature); } @Override diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java index 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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7994fccbd650..94a5099b45da 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3177,7 +3177,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void reportPerceptible(IBinder windowToken, boolean perceptible) { + public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) { Objects.requireNonNull(windowToken, "windowToken must not be null"); int uid = Binder.getCallingUid(); synchronized (mMethodMap) { @@ -5992,9 +5992,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void reportStartInput(IBinder startInputToken, IVoidResultCallback resultCallback) { - CallbackUtils.onResult(resultCallback, - () -> mImms.reportStartInput(mToken, startInputToken)); + public void reportStartInputAsync(IBinder startInputToken) { + mImms.reportStartInput(mToken, startInputToken); } @BinderThread diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 62447439003b..885093d61486 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1840,7 +1840,7 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override - public void reportPerceptible(IBinder windowClient, boolean perceptible) { + public void reportPerceptibleAsync(IBinder windowClient, boolean perceptible) { reportNotSupported(); } diff --git a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 045e06d001e6..2ffc62a0731b 100644 --- a/services/core/java/com/android/server/location/eventlog/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -178,8 +178,9 @@ public class LocationEventLog extends LocalEventLog { } /** Logs that a provider has entered or exited stationary throttling. */ - public void logProviderStationaryThrottled(String provider, boolean throttled) { - addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled); + public void logProviderStationaryThrottled(String provider, boolean throttled, + ProviderRequest request) { + addLogEvent(EVENT_PROVIDER_STATIONARY_THROTTLED, provider, throttled, request); } /** Logs that the location power save mode has changed. */ @@ -217,7 +218,7 @@ public class LocationEventLog extends LocalEventLog { (Integer) args[1], (CallerIdentity) args[2]); case EVENT_PROVIDER_STATIONARY_THROTTLED: return new ProviderStationaryThrottledEvent(timeDelta, (String) args[0], - (Boolean) args[1]); + (Boolean) args[1], (ProviderRequest) args[2]); case EVENT_LOCATION_POWER_SAVE_MODE_CHANGE: return new LocationPowerSaveModeEvent(timeDelta, (Integer) args[0]); default: @@ -355,17 +356,19 @@ public class LocationEventLog extends LocalEventLog { private static final class ProviderStationaryThrottledEvent extends ProviderEvent { private final boolean mStationaryThrottled; + private final ProviderRequest mRequest; ProviderStationaryThrottledEvent(long timeDelta, String provider, - boolean stationaryThrottled) { + boolean stationaryThrottled, ProviderRequest request) { super(timeDelta, provider); mStationaryThrottled = stationaryThrottled; + mRequest = request; } @Override public String getLogString() { return mProvider + " provider stationary/idle " + (mStationaryThrottled ? "throttled" - : "unthrottled"); + : "unthrottled") + ", request = " + mRequest; } } diff --git a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java index ab7e526a8e68..22a675ad39ab 100644 --- a/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java +++ b/services/core/java/com/android/server/location/provider/StationaryThrottlingLocationProvider.java @@ -206,7 +206,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation if (D) { Log.d(TAG, mName + " provider stationary throttled"); } - EVENT_LOG.logProviderStationaryThrottled(mName, true); + EVENT_LOG.logProviderStationaryThrottled(mName, true, mOutgoingRequest); } if (mDeliverLastLocationCallback != null) { @@ -224,7 +224,7 @@ public final class StationaryThrottlingLocationProvider extends DelegateLocation } } else { if (oldThrottlingIntervalMs != INTERVAL_DISABLED) { - EVENT_LOG.logProviderStationaryThrottled(mName, false); + EVENT_LOG.logProviderStationaryThrottled(mName, false, mOutgoingRequest); if (D) { Log.d(TAG, mName + " provider stationary unthrottled"); } diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 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/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index 8bd3b1e0b6ac..42b7c9d388a0 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -100,7 +100,7 @@ public class NotificationHistoryDatabase { IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION); deletionFilter.addDataScheme(SCHEME_DELETION); - mContext.registerReceiver(mFileCleaupReceiver, deletionFilter); + mContext.registerReceiver(mFileCleanupReceiver, deletionFilter); } public void init() { @@ -273,13 +273,36 @@ public class NotificationHistoryDatabase { } } + /** + * Remove the first entry from the list of history files whose file matches the given file path. + * + * This method is necessary for anything that only has an absolute file path rather than an + * AtomicFile object from the list of history files. + * + * filePath should be an absolute path. + */ + void removeFilePathFromHistory(String filePath) { + if (filePath == null) { + return; + } + + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); + while (historyFileItr.hasNext()) { + final AtomicFile af = historyFileItr.next(); + if (af != null && filePath.equals(af.getBaseFile().getAbsolutePath())) { + historyFileItr.remove(); + return; + } + } + } + private void deleteFile(AtomicFile file) { if (DEBUG) { Slog.d(TAG, "Removed " + file.getBaseFile().getName()); } file.delete(); // TODO: delete all relevant bitmaps, once they exist - mHistoryFiles.remove(file); + removeFilePathFromHistory(file.getBaseFile().getAbsolutePath()); } private void scheduleDeletion(File file, long creationTime, int retentionDays) { @@ -342,7 +365,7 @@ public class NotificationHistoryDatabase { } } - private final BroadcastReceiver mFileCleaupReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -358,7 +381,7 @@ public class NotificationHistoryDatabase { Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName()); } fileToDelete.delete(); - mHistoryFiles.remove(fileToDelete); + removeFilePathFromHistory(filePath); } } catch (Exception e) { Slog.e(TAG, "Failed to delete notification history file", e); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 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/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 6875b8a5abeb..ec79483e0f34 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -45,6 +45,7 @@ public final class DumpState { public static final int DUMP_QUERIES = 1 << 26; public static final int DUMP_KNOWN_PACKAGES = 1 << 27; public static final int DUMP_PER_UID_READ_TIMEOUTS = 1 << 28; + public static final int DUMP_SNAPSHOT_STATISTICS = 1 << 29; public static final int OPTION_SHOW_FILTERS = 1 << 0; public static final int OPTION_DUMP_ALL_COMPONENTS = 1 << 1; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 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..59039baf2764 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1914,6 +1914,18 @@ public class PackageManagerService extends IPackageManager.Stub */ private interface Computer { + /** + * Administrative statistics: record that the snapshot has been used. Every call + * to use() increments the usage counter. + */ + void use(); + + /** + * Fetch the snapshot usage counter. + * @return The number of times this snapshot was used. + */ + int getUsed(); + @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits); @@ -2065,6 +2077,9 @@ public class PackageManagerService extends IPackageManager.Stub */ private static class ComputerEngine implements Computer { + // The administrative use counter. + private int mUsed = 0; + // Cached attributes. The names in this class are the same as the // names in PackageManagerService; see that class for documentation. protected final Settings mSettings; @@ -2157,6 +2172,20 @@ public class PackageManagerService extends IPackageManager.Stub mService = args.service; } + /** + * Record that the snapshot was used. + */ + public void use() { + mUsed++; + } + + /** + * Return the usage counter. + */ + public int getUsed() { + return mUsed; + } + public @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent, String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags, int filterCallingUid, int userId, boolean resolveForStart, @@ -4885,121 +4914,11 @@ public class PackageManagerService extends IPackageManager.Stub */ private final Object mSnapshotLock = new Object(); - // A counter of all queries that hit the current snapshot. - @GuardedBy("mSnapshotLock") - private int mSnapshotHits = 0; - - // A class to record snapshot statistics. - private static class SnapshotStatistics { - // A build time is "big" if it takes longer than 5ms. - private static final long SNAPSHOT_BIG_BUILD_TIME_NS = TimeUnit.MILLISECONDS.toNanos(5); - - // A snapshot is in quick succession to the previous snapshot if it less than - // 100ms since the previous snapshot. - private static final long SNAPSHOT_QUICK_REBUILD_INTERVAL_NS = - TimeUnit.MILLISECONDS.toNanos(100); - - // The interval between snapshot statistics logging, in ns. - private static final long SNAPSHOT_LOG_INTERVAL_NS = TimeUnit.MINUTES.toNanos(10); - - // The throttle parameters for big build reporting. Do not report more than this - // many events in a single log interval. - private static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10; - - // The time the snapshot statistics were last logged. - private long mStatisticsSent = 0; - - // The number of build events logged since the last periodic log. - private int mLoggedBuilds = 0; - - // The time of the last build. - private long mLastBuildTime = 0; - - // The number of times the snapshot has been rebuilt since the statistics were - // last logged. - private int mRebuilds = 0; - - // The number of times the snapshot has been used since it was rebuilt. - private int mReused = 0; - - // The number of "big" build times since the last log. "Big" is defined by - // SNAPSHOT_BIG_BUILD_TIME. - private int mBigBuilds = 0; - - // The number of quick rebuilds. "Quick" is defined by - // SNAPSHOT_QUICK_REBUILD_INTERVAL_NS. - private int mQuickRebuilds = 0; - - // The time take to build a snapshot. This is cumulative over the rebuilds recorded - // in mRebuilds, so the average time to build a snapshot is given by - // mBuildTimeNs/mRebuilds. - private int mBuildTimeNs = 0; - - // The maximum build time since the last log. - private long mMaxBuildTimeNs = 0; - - // The constant that converts ns to ms. This is the divisor. - private final long NS_TO_MS = TimeUnit.MILLISECONDS.toNanos(1); - - // Convert ns to an int ms. The maximum range of this method is about 24 days. - // There is no expectation that an event will take longer than that. - private int nsToMs(long ns) { - return (int) (ns / NS_TO_MS); - } - - // The single method records a rebuild. The "now" parameter is passed in because - // the caller needed it to computer the duration, so pass it in to avoid - // recomputing it. - private void rebuild(long now, long done, int hits) { - if (mStatisticsSent == 0) { - mStatisticsSent = now; - } - final long elapsed = now - mLastBuildTime; - final long duration = done - now; - mLastBuildTime = now; - - if (mMaxBuildTimeNs < duration) { - mMaxBuildTimeNs = duration; - } - mRebuilds++; - mReused += hits; - mBuildTimeNs += duration; - - boolean log_build = false; - if (duration > SNAPSHOT_BIG_BUILD_TIME_NS) { - log_build = true; - mBigBuilds++; - } - if (elapsed < SNAPSHOT_QUICK_REBUILD_INTERVAL_NS) { - log_build = true; - mQuickRebuilds++; - } - if (log_build && mLoggedBuilds < SNAPSHOT_BUILD_REPORT_LIMIT) { - EventLogTags.writePmSnapshotRebuild(nsToMs(duration), nsToMs(elapsed)); - mLoggedBuilds++; - } - - final long log_interval = now - mStatisticsSent; - if (log_interval >= SNAPSHOT_LOG_INTERVAL_NS) { - EventLogTags.writePmSnapshotStats(mRebuilds, mReused, - mBigBuilds, mQuickRebuilds, - nsToMs(mMaxBuildTimeNs), - nsToMs(mBuildTimeNs)); - mStatisticsSent = now; - mRebuilds = 0; - mReused = 0; - mBuildTimeNs = 0; - mMaxBuildTimeNs = 0; - mBigBuilds = 0; - mQuickRebuilds = 0; - mLoggedBuilds = 0; - } - } - } - - // Snapshot statistics. - @GuardedBy("mLock") - private final SnapshotStatistics mSnapshotStatistics = new SnapshotStatistics(); + /** + * The snapshot statistics. These are collected to track performance and to identify + * situations in which the snapshots are misbehaving. + */ + private final SnapshotStatistics mSnapshotStatistics; // The snapshot disable/enable switch. An image with the flag set true uses snapshots // and an image with the flag set false does not use snapshots. @@ -5033,10 +4952,9 @@ public class PackageManagerService extends IPackageManager.Stub Computer c = mSnapshotComputer; if (sSnapshotCorked && (c != null)) { // Snapshots are corked, which means new ones should not be built right now. + c.use(); return c; } - // Deliberately capture the value pre-increment - final int hits = mSnapshotHits++; if (sSnapshotInvalid || (c == null)) { // The snapshot is invalid if it is marked as invalid or if it is null. If it // is null, then it is currently being rebuilt by rebuildSnapshot(). @@ -5046,7 +4964,7 @@ public class PackageManagerService extends IPackageManager.Stub // self-consistent (the lock is being held) and is current as of the time // this function is entered. if (sSnapshotInvalid) { - rebuildSnapshot(hits); + rebuildSnapshot(); } // Guaranteed to be non-null. mSnapshotComputer is only be set to null @@ -5056,6 +4974,7 @@ public class PackageManagerService extends IPackageManager.Stub c = mSnapshotComputer; } } + c.use(); return c; } } @@ -5065,16 +4984,16 @@ public class PackageManagerService extends IPackageManager.Stub * threads from using the invalid computer until it is rebuilt. */ @GuardedBy("mLock") - private void rebuildSnapshot(int hits) { - final long now = System.nanoTime(); + private void rebuildSnapshot() { + final long now = SystemClock.currentTimeMicro(); + final int hits = mSnapshotComputer == null ? -1 : mSnapshotComputer.getUsed(); mSnapshotComputer = null; sSnapshotInvalid = false; final Snapshot args = new Snapshot(Snapshot.SNAPPED); mSnapshotComputer = new ComputerEngine(args); - final long done = System.nanoTime(); + final long done = SystemClock.currentTimeMicro(); mSnapshotStatistics.rebuild(now, done, hits); - mSnapshotHits = 0; } /** @@ -6327,6 +6246,7 @@ public class PackageManagerService extends IPackageManager.Stub mSnapshotEnabled = false; mLiveComputer = createLiveComputer(); mSnapshotComputer = null; + mSnapshotStatistics = null; mPackages.putAll(testParams.packages); mEnableFreeCacheV2 = testParams.enableFreeCacheV2; @@ -6479,17 +6399,20 @@ public class PackageManagerService extends IPackageManager.Stub mDomainVerificationManager = injector.getDomainVerificationManagerInternal(); mDomainVerificationManager.setConnection(mDomainVerificationConnection); - // Create the computer as soon as the state objects have been installed. The - // cached computer is the same as the live computer until the end of the - // constructor, at which time the invalidation method updates it. The cache is - // corked initially to ensure a cached computer is not built until the end of the - // constructor. - mSnapshotEnabled = SNAPSHOT_ENABLED; - sSnapshotCorked = true; - sSnapshotInvalid = true; - mLiveComputer = createLiveComputer(); - mSnapshotComputer = null; - registerObserver(); + synchronized (mLock) { + // Create the computer as soon as the state objects have been installed. The + // cached computer is the same as the live computer until the end of the + // constructor, at which time the invalidation method updates it. The cache is + // corked initially to ensure a cached computer is not built until the end of the + // constructor. + mSnapshotEnabled = SNAPSHOT_ENABLED; + sSnapshotCorked = true; + sSnapshotInvalid = true; + mSnapshotStatistics = new SnapshotStatistics(); + mLiveComputer = createLiveComputer(); + mSnapshotComputer = null; + registerObserver(); + } // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { @@ -15315,9 +15238,8 @@ 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 +15249,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 +22118,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) { } @@ -23959,6 +23880,7 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(" dexopt: dump dexopt state"); pw.println(" compiler-stats: dump compiler statistics"); pw.println(" service-permissions: dump permissions required by services"); + pw.println(" snapshot: dump snapshot statistics"); pw.println(" known-packages: dump known packages"); pw.println(" <package.name>: info about given package"); return; @@ -24107,6 +24029,8 @@ public class PackageManagerService extends IPackageManager.Stub dumpState.setDump(DumpState.DUMP_KNOWN_PACKAGES); } else if ("t".equals(cmd) || "timeouts".equals(cmd)) { dumpState.setDump(DumpState.DUMP_PER_UID_READ_TIMEOUTS); + } else if ("snapshot".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_SNAPSHOT_STATISTICS); } else if ("write".equals(cmd)) { synchronized (mLock) { writeSettingsLPrTEMP(); @@ -24435,6 +24359,22 @@ public class PackageManagerService extends IPackageManager.Stub pw.println(")"); } } + + if (!checkin && dumpState.isDumping(DumpState.DUMP_SNAPSHOT_STATISTICS)) { + pw.println("Snapshot statistics"); + if (!mSnapshotEnabled) { + pw.println(" Snapshots disabled"); + } else { + int hits = 0; + synchronized (mSnapshotLock) { + if (mSnapshotComputer != null) { + hits = mSnapshotComputer.getUsed(); + } + } + final long now = SystemClock.currentTimeMicro(); + mSnapshotStatistics.dump(pw, " ", now, hits, true); + } + } } /** @@ -27810,8 +27750,8 @@ 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/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java new file mode 100644 index 000000000000..c425bad50ae8 --- /dev/null +++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java @@ -0,0 +1,622 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.text.TextUtils; + +import com.android.server.EventLogTags; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Locale; + +/** + * This class records statistics about PackageManagerService snapshots. It maintains two sets of + * statistics: a periodic set which represents the last 10 minutes, and a cumulative set since + * process boot. The key metrics that are recorded are: + * <ul> + * <li> The time to create a snapshot - this is the performance cost of a snapshot + * <li> The lifetime of the snapshot - creation time over lifetime is the amortized cost + * <li> The number of times a snapshot is reused - this is the number of times lock + * contention was avoided. + * </ul> + + * The time conversions in this class are designed to keep arithmetic using ints, rather + * than longs. Raw times are supplied as longs in units of us. These are left long. + * Rebuild durations however, are converted to ints. An int can express a duration of + * approximately 35 minutes. This is longer than any expected snapshot rebuild time, so + * an int is satisfactory. The exception is the cumulative rebuild time over the course + * of a monitoring cycle: this value is kept long since the cycle time is one week and in + * a badly behaved system, the rebuild time might exceed 35 minutes. + + * @hide + */ +public class SnapshotStatistics { + /** + * The interval at which statistics should be ticked. It is 60s. The interval is in + * units of milliseconds because that is what's required by Handler.sendMessageDelayed(). + */ + public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000; + + /** + * The number of ticks for long statistics. This is one week. + */ + public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60; + + /** + * The number snapshot event logs that can be generated in a single logging interval. + * A small number limits the logging generated by this class. A snapshot event log is + * generated for every big snapshot build time, up to the limit, or whenever the + * maximum build time is exceeded in the logging interval. + */ + public static final int SNAPSHOT_BUILD_REPORT_LIMIT = 10; + + /** + * The number of microseconds in a millisecond. + */ + private static final int US_IN_MS = 1000; + + /** + * A snapshot build time is "big" if it takes longer than 10ms. + */ + public static final int SNAPSHOT_BIG_BUILD_TIME_US = 10 * US_IN_MS; + + /** + * A snapshot build time is reportable if it takes longer than 30ms. Testing shows + * that this is very rare. + */ + public static final int SNAPSHOT_REPORTABLE_BUILD_TIME_US = 30 * US_IN_MS; + + /** + * A snapshot is short-lived it used fewer than 5 times. + */ + public static final int SNAPSHOT_SHORT_LIFETIME = 5; + + /** + * The lock to control access to this object. + */ + private final Object mLock = new Object(); + + /** + * The bins for the build time histogram. Values are in us. + */ + private final BinMap mTimeBins; + + /** + * The bins for the snapshot use histogram. + */ + private final BinMap mUseBins; + + /** + * The number of events reported in the current tick. + */ + private int mEventsReported = 0; + + /** + * The tick counter. At the default tick interval, this wraps every 4000 years or so. + */ + private int mTicks = 0; + + /** + * The handler used for the periodic ticks. + */ + private Handler mHandler = null; + + /** + * Convert ns to an int ms. The maximum range of this method is about 24 days. There + * is no expectation that an event will take longer than that. + */ + private int usToMs(int us) { + return us / US_IN_MS; + } + + /** + * This class exists to provide a fast bin lookup for histograms. An instance has an + * integer array that maps incoming values to bins. Values larger than the array are + * mapped to the top-most bin. + */ + private static class BinMap { + + // The number of bins + private int mCount; + // The mapping of low integers to bins + private int[] mBinMap; + // The maximum mapped value. Values at or above this are mapped to the + // top bin. + private int mMaxBin; + // A copy of the original key + private int[] mUserKey; + + /** + * Create a bin map. The input is an array of integers, which must be + * monotonically increasing (this is not checked). The result is an integer array + * as long as the largest value in the input. + */ + BinMap(int[] userKey) { + mUserKey = Arrays.copyOf(userKey, userKey.length); + // The number of bins is the length of the keys, plus 1 (for the max). + mCount = mUserKey.length + 1; + // The maximum value is one more than the last one in the map. + mMaxBin = mUserKey[mUserKey.length - 1] + 1; + mBinMap = new int[mMaxBin + 1]; + + int j = 0; + for (int i = 0; i < mUserKey.length; i++) { + while (j <= mUserKey[i]) { + mBinMap[j] = i; + j++; + } + } + mBinMap[mMaxBin] = mUserKey.length; + } + + /** + * Map a value to a bin. + */ + public int getBin(int x) { + if (x >= 0 && x < mMaxBin) { + return mBinMap[x]; + } else if (x >= mMaxBin) { + return mBinMap[mMaxBin]; + } else { + // x is negative. The bin will not be used. + return 0; + } + } + + /** + * The number of bins in this map + */ + public int count() { + return mCount; + } + + /** + * For convenience, return the user key. + */ + public int[] userKeys() { + return mUserKey; + } + } + + /** + * A complete set of statistics. These are public, making it simpler for a client to + * fetch the individual fields. + */ + public class Stats { + + /** + * The start time for this set of statistics, in us. + */ + public long mStartTimeUs = 0; + + /** + * The completion time for this set of statistics, in ns. A value of zero means + * the statistics are still active. + */ + public long mStopTimeUs = 0; + + /** + * The build-time histogram. The total number of rebuilds is the sum over the + * histogram entries. + */ + public int[] mTimes; + + /** + * The reuse histogram. The total number of snapshot uses is the sum over the + * histogram entries. + */ + public int[] mUsed; + + /** + * The total number of rebuilds. This could be computed by summing over the use + * bins, but is maintained separately for convenience. + */ + public int mTotalBuilds = 0; + + /** + * The total number of times any snapshot was used. + */ + public int mTotalUsed = 0; + + /** + * The total number of builds that count as big, which means they took longer than + * SNAPSHOT_BIG_BUILD_TIME_NS. + */ + public int mBigBuilds = 0; + + /** + * The total number of short-lived snapshots + */ + public int mShortLived = 0; + + /** + * The time taken to build snapshots. This is cumulative over the rebuilds + * recorded in mRebuilds, so the average time to build a snapshot is given by + * mBuildTimeNs/mRebuilds. Note that this cannot be computed from the histogram. + */ + public long mTotalTimeUs = 0; + + /** + * The maximum build time since the last log. + */ + public int mMaxBuildTimeUs = 0; + + /** + * Record the rebuild. The parameters are the length of time it took to build the + * latest snapshot, and the number of times the _previous_ snapshot was used. A + * negative value for used signals an invalid value, which is the case the first + * time a snapshot is every built. + */ + private void rebuild(int duration, int used, + int buildBin, int useBin, boolean big, boolean quick) { + mTotalBuilds++; + mTimes[buildBin]++; + + if (used >= 0) { + mTotalUsed += used; + mUsed[useBin]++; + } + + mTotalTimeUs += duration; + boolean reportIt = false; + + if (big) { + mBigBuilds++; + } + if (quick) { + mShortLived++; + } + if (mMaxBuildTimeUs < duration) { + mMaxBuildTimeUs = duration; + } + } + + private Stats(long now) { + mStartTimeUs = now; + mTimes = new int[mTimeBins.count()]; + mUsed = new int[mUseBins.count()]; + } + + /** + * Create a copy of the argument. The copy is made under lock but can then be + * used without holding the lock. + */ + private Stats(Stats orig) { + mStartTimeUs = orig.mStartTimeUs; + mStopTimeUs = orig.mStopTimeUs; + mTimes = Arrays.copyOf(orig.mTimes, orig.mTimes.length); + mUsed = Arrays.copyOf(orig.mUsed, orig.mUsed.length); + mTotalBuilds = orig.mTotalBuilds; + mTotalUsed = orig.mTotalUsed; + mBigBuilds = orig.mBigBuilds; + mShortLived = orig.mShortLived; + mTotalTimeUs = orig.mTotalTimeUs; + mMaxBuildTimeUs = orig.mMaxBuildTimeUs; + } + + /** + * Set the end time for the statistics. The end time is used only for reporting + * in the dump() method. + */ + private void complete(long stop) { + mStopTimeUs = stop; + } + + /** + * Format a time span into ddd:HH:MM:SS. The input is in us. + */ + private String durationToString(long us) { + // s has a range of several years + int s = (int) (us / (1000 * 1000)); + int m = s / 60; + s %= 60; + int h = m / 60; + m %= 60; + int d = h / 24; + h %= 24; + if (d != 0) { + return TextUtils.formatSimple("%2d:%02d:%02d:%02d", d, h, m, s); + } else if (h != 0) { + return TextUtils.formatSimple("%2s %02d:%02d:%02d", "", h, m, s); + } else { + return TextUtils.formatSimple("%2s %2s %2d:%02d", "", "", m, s); + } + } + + /** + * Print the prefix for dumping. This does not generate a line to the output. + */ + private void dumpPrefix(PrintWriter pw, String indent, long now, boolean header, + String title) { + pw.print(indent + " "); + if (header) { + pw.format(Locale.US, "%-23s", title); + } else { + pw.format(Locale.US, "%11s", durationToString(now - mStartTimeUs)); + if (mStopTimeUs != 0) { + pw.format(Locale.US, " %11s", durationToString(now - mStopTimeUs)); + } else { + pw.format(Locale.US, " %11s", "now"); + } + } + } + + /** + * Dump the summary statistics record. Choose the header or the data. + * number of builds + * number of uses + * number of big builds + * number of short lifetimes + * cumulative build time, in seconds + * maximum build time, in ms + */ + private void dumpStats(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Summary stats"); + if (header) { + pw.format(Locale.US, " %10s %10s %10s %10s %10s %10s", + "TotBlds", "TotUsed", "BigBlds", "ShortLvd", + "TotTime", "MaxTime"); + } else { + pw.format(Locale.US, + " %10d %10d %10d %10d %10d %10d", + mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived, + mTotalTimeUs / 1000, mMaxBuildTimeUs / 1000); + } + pw.println(); + } + + /** + * Dump the build time histogram. Choose the header or the data. + */ + private void dumpTimes(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Build times"); + if (header) { + int[] keys = mTimeBins.userKeys(); + for (int i = 0; i < keys.length; i++) { + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("<= %dms", keys[i])); + } + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("> %dms", keys[keys.length - 1])); + } else { + for (int i = 0; i < mTimes.length; i++) { + pw.format(Locale.US, " %10d", mTimes[i]); + } + } + pw.println(); + } + + /** + * Dump the usage histogram. Choose the header or the data. + */ + private void dumpUsage(PrintWriter pw, String indent, long now, boolean header) { + dumpPrefix(pw, indent, now, header, "Use counters"); + if (header) { + int[] keys = mUseBins.userKeys(); + for (int i = 0; i < keys.length; i++) { + pw.format(Locale.US, " %10s", TextUtils.formatSimple("<= %d", keys[i])); + } + pw.format(Locale.US, " %10s", + TextUtils.formatSimple("> %d", keys[keys.length - 1])); + } else { + for (int i = 0; i < mUsed.length; i++) { + pw.format(Locale.US, " %10d", mUsed[i]); + } + } + pw.println(); + } + + /** + * Dump something, based on the "what" parameter. + */ + private void dump(PrintWriter pw, String indent, long now, boolean header, String what) { + if (what.equals("stats")) { + dumpStats(pw, indent, now, header); + } else if (what.equals("times")) { + dumpTimes(pw, indent, now, header); + } else if (what.equals("usage")) { + dumpUsage(pw, indent, now, header); + } else { + throw new IllegalArgumentException("unrecognized choice: " + what); + } + } + + /** + * Report the object via an event. Presumably the record indicates an anomalous + * incident. + */ + private void report() { + EventLogTags.writePmSnapshotStats( + mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived, + mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS); + } + } + + /** + * Long statistics. These roll over approximately every week. + */ + private Stats[] mLong; + + /** + * Short statistics. These roll over approximately every minute; + */ + private Stats[] mShort; + + /** + * The time of the last build. This can be used to compute the length of time a + * snapshot existed before being replaced. + */ + private long mLastBuildTime = 0; + + /** + * Create a snapshot object. Initialize the bin levels. The last bin catches + * everything that is not caught earlier, so its value is not really important. + */ + public SnapshotStatistics() { + // Create the bin thresholds. The time bins are in units of us. + mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); + mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 }); + + // Create the raw statistics + final long now = SystemClock.currentTimeMicro(); + mLong = new Stats[2]; + mLong[0] = new Stats(now); + mShort = new Stats[10]; + mShort[0] = new Stats(now); + + // Create the message handler for ticks and start the ticker. + mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + SnapshotStatistics.this.handleMessage(msg); + } + }; + scheduleTick(); + } + + /** + * Handle a message. The only messages are ticks, so the message parameter is ignored. + */ + private void handleMessage(@Nullable Message msg) { + tick(); + scheduleTick(); + } + + /** + * Schedule one tick, a tick interval in the future. + */ + private void scheduleTick() { + mHandler.sendEmptyMessageDelayed(0, SNAPSHOT_TICK_INTERVAL_MS); + } + + /** + * Record a rebuild. Cumulative and current statistics are updated. Events may be + * generated. + * @param now The time at which the snapshot rebuild began, in ns. + * @param done The time at which the snapshot rebuild completed, in ns. + * @param hits The number of times the previous snapshot was used. + */ + public void rebuild(long now, long done, int hits) { + // The duration has a span of about 2000s + final int duration = (int) (done - now); + boolean reportEvent = false; + synchronized (mLock) { + mLastBuildTime = now; + + final int timeBin = mTimeBins.getBin(duration / 1000); + final int useBin = mUseBins.getBin(hits); + final boolean big = duration >= SNAPSHOT_BIG_BUILD_TIME_US; + final boolean quick = hits <= SNAPSHOT_SHORT_LIFETIME; + + mShort[0].rebuild(duration, hits, timeBin, useBin, big, quick); + mLong[0].rebuild(duration, hits, timeBin, useBin, big, quick); + if (duration >= SNAPSHOT_REPORTABLE_BUILD_TIME_US) { + if (mEventsReported++ < SNAPSHOT_BUILD_REPORT_LIMIT) { + reportEvent = true; + } + } + } + // The IO to the logger is done outside the lock. + if (reportEvent) { + // Report the first N big builds, and every new maximum after that. + EventLogTags.writePmSnapshotRebuild(duration / US_IN_MS, hits); + } + } + + /** + * Roll a stats array. Shift the elements up an index and create a new element at + * index zero. The old element zero is completed with the specified time. + */ + private void shift(Stats[] s, long now) { + s[0].complete(now); + for (int i = s.length - 1; i > 0; i--) { + s[i] = s[i - 1]; + } + s[0] = new Stats(now); + } + + /** + * Roll the statistics. + * <ul> + * <li> Roll the quick statistics immediately. + * <li> Roll the long statistics every SNAPSHOT_LONG_TICKER ticks. The long + * statistics hold a week's worth of data. + * <li> Roll the logging statistics every SNAPSHOT_LOGGING_TICKER ticks. The logging + * statistics hold 10 minutes worth of data. + * </ul> + */ + private void tick() { + synchronized (mLock) { + long now = SystemClock.currentTimeMicro(); + mTicks++; + if (mTicks % SNAPSHOT_LONG_TICKS == 0) { + shift(mLong, now); + } + shift(mShort, now); + mEventsReported = 0; + } + } + + /** + * Dump the statistics. The header is dumped from l[0], so that must not be null. + */ + private void dump(PrintWriter pw, String indent, long now, Stats[] l, Stats[] s, String what) { + l[0].dump(pw, indent, now, true, what); + for (int i = 0; i < s.length; i++) { + if (s[i] != null) { + s[i].dump(pw, indent, now, false, what); + } + } + for (int i = 0; i < l.length; i++) { + if (l[i] != null) { + l[i].dump(pw, indent, now, false, what); + } + } + } + + /** + * Dump the statistics. The format is compatible with the PackageManager dumpsys + * output. + */ + public void dump(PrintWriter pw, String indent, long now, int unrecorded, boolean full) { + // Grab the raw statistics under lock, but print them outside of the lock. + Stats[] l; + Stats[] s; + synchronized (mLock) { + l = Arrays.copyOf(mLong, mLong.length); + l[0] = new Stats(l[0]); + s = Arrays.copyOf(mShort, mShort.length); + s[0] = new Stats(s[0]); + } + pw.format(Locale.US, "%s Unrecorded hits %d", indent, unrecorded); + pw.println(); + dump(pw, indent, now, l, s, "stats"); + if (!full) { + return; + } + pw.println(); + dump(pw, indent, now, l, s, "times"); + pw.println(); + dump(pw, indent, now, l, s, "usage"); + } +} diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING index 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/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index e4dc8c2bf797..f02107287123 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -96,6 +96,8 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; +import com.android.server.apphibernation.AppHibernationManagerInternal; +import com.android.server.apphibernation.AppHibernationService; import java.util.ArrayList; import java.util.LinkedList; @@ -163,6 +165,9 @@ class ActivityMetricsLogger { */ private final LaunchObserverRegistryImpl mLaunchObserver; @VisibleForTesting static final int LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE = 512; + private final ArrayMap<String, Boolean> mLastHibernationStates = new ArrayMap<>(); + private AppHibernationManagerInternal mAppHibernationManagerInternal; + private boolean mIsAppHibernationEnabled; /** * The information created when an intent is incoming but we do not yet know whether it will be @@ -789,6 +794,27 @@ class ActivityMetricsLogger { } } + @Nullable + private AppHibernationManagerInternal getAppHibernationManagerInternal() { + if (mAppHibernationManagerInternal == null) { + mIsAppHibernationEnabled = AppHibernationService.isAppHibernationEnabled(); + mAppHibernationManagerInternal = + LocalServices.getService(AppHibernationManagerInternal.class); + } + return mAppHibernationManagerInternal; + } + + /** + * Notifies the tracker before the package is unstopped because of launching activity. + * @param packageName The package to be unstopped. + */ + void notifyBeforePackageUnstopped(@NonNull String packageName) { + final AppHibernationManagerInternal ahmInternal = getAppHibernationManagerInternal(); + if (ahmInternal != null && mIsAppHibernationEnabled) { + mLastHibernationStates.put(packageName, ahmInternal.isHibernatingGlobally(packageName)); + } + } + /** * Notifies the tracker that we called immediately before we call bindApplication on the client. * @@ -823,6 +849,8 @@ class ActivityMetricsLogger { } stopLaunchTrace(info); + final Boolean isHibernating = + mLastHibernationStates.remove(info.mLastLaunchedActivity.packageName); if (abort) { mSupervisor.stopWaitingForActivityVisible(info.mLastLaunchedActivity); launchObserverNotifyActivityLaunchCancelled(info); @@ -830,7 +858,7 @@ class ActivityMetricsLogger { if (info.isInterestingToLoggerAndObserver()) { launchObserverNotifyActivityLaunchFinished(info, timestampNs); } - logAppTransitionFinished(info); + logAppTransitionFinished(info, isHibernating != null ? isHibernating : false); } info.mPendingDrawActivities.clear(); mTransitionInfoList.remove(info); @@ -859,7 +887,7 @@ class ActivityMetricsLogger { } } - private void logAppTransitionFinished(@NonNull TransitionInfo info) { + private void logAppTransitionFinished(@NonNull TransitionInfo info, boolean isHibernating) { if (DEBUG_METRICS) Slog.i(TAG, "logging finished transition " + info); // Take a snapshot of the transition info before sending it to the handler for logging. @@ -868,7 +896,7 @@ class ActivityMetricsLogger { if (info.isInterestingToLoggerAndObserver()) { BackgroundThread.getHandler().post(() -> logAppTransition( info.mCurrentTransitionDeviceUptime, info.mCurrentTransitionDelayMs, - infoSnapshot)); + infoSnapshot, isHibernating)); } BackgroundThread.getHandler().post(() -> logAppDisplayed(infoSnapshot)); if (info.mPendingFullyDrawn != null) { @@ -880,7 +908,7 @@ class ActivityMetricsLogger { // This gets called on a background thread without holding the activity manager lock. private void logAppTransition(int currentTransitionDeviceUptime, int currentTransitionDelayMs, - TransitionInfoSnapshot info) { + TransitionInfoSnapshot info, boolean isHibernating) { final LogMaker builder = new LogMaker(APP_TRANSITION); builder.setPackageName(info.packageName); builder.setType(info.type); @@ -933,7 +961,8 @@ class ActivityMetricsLogger { packageOptimizationInfo.getCompilationReason(), packageOptimizationInfo.getCompilationFilter(), info.sourceType, - info.sourceEventDelayMs); + info.sourceEventDelayMs, + isHibernating); if (DEBUG_METRICS) { Slog.i(TAG, String.format("APP_START_OCCURRED(%s, %s, %s, %s, %s)", diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c8b7038fd3c2..3e3ff1f3d9f2 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -587,7 +587,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A AnimatingActivityRegistry mAnimatingActivityRegistry; - private Task mLastParent; + // Set to the previous Task parent of the ActivityRecord when it is reparented to a new Task + // due to picture-in-picture. This gets cleared whenever this activity or the Task + // it references to gets removed. This should also be cleared when we move out of pip. + private Task mLastParentBeforePip; boolean firstWindowDrawn; /** Whether the visible window(s) of this activity is drawn. */ @@ -1096,6 +1099,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "configChanges=0x" + Integer.toHexString(info.configChanges)); } } + if (mLastParentBeforePip != null) { + pw.println(prefix + "lastParentTaskIdBeforePip=" + mLastParentBeforePip.mTaskId); + } dumpLetterboxInfo(pw, prefix); } @@ -1133,6 +1139,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + " letterboxBackgroundWallpaperBlurRadius=" + getLetterboxWallpaperBlurRadius()); } + pw.println(prefix + " letterboxHorizontalPositionMultiplier=" + + mWmService.getLetterboxHorizontalPositionMultiplier()); } /** @@ -1349,7 +1357,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (getDisplayContent() != null) { getDisplayContent().mClosingApps.remove(this); } - } else if (mLastParent != null && mLastParent.getRootTask() != null) { + } else if (oldTask != null && oldTask.getRootTask() != null) { task.getRootTask().mExitingActivities.remove(this); } final Task rootTask = getRootTask(); @@ -1362,7 +1370,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? rootTask.getAnimatingActivityRegistry() : null; - mLastParent = task; + if (task == mLastParentBeforePip) { + // Activity's reparented back from pip, clear the links once established + clearLastParentBeforePip(); + } updateColorTransform(); @@ -1381,6 +1392,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** + * Sets {@link #mLastParentBeforePip} to the current parent Task, it's caller's job to ensure + * {@link #getTask()} is set before this is called. + */ + void setLastParentBeforePip() { + mLastParentBeforePip = getTask(); + mLastParentBeforePip.mChildPipActivity = this; + } + + private void clearLastParentBeforePip() { + if (mLastParentBeforePip != null) { + mLastParentBeforePip.mChildPipActivity = null; + mLastParentBeforePip = null; + } + } + + @Nullable Task getLastParentBeforePip() { + return mLastParentBeforePip; + } + private void updateColorTransform() { if (mSurfaceControl != null && mLastAppSaturationInfo != null) { getPendingTransaction().setColorTransform(mSurfaceControl, @@ -2140,7 +2171,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s", this, startingData); - WindowManagerPolicy.StartingSurface surface = null; try { surface = startingData.createStartingSurface(ActivityRecord.this); @@ -3434,6 +3464,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ void cleanUp(boolean cleanServices, boolean setState) { task.cleanUpActivityReferences(this); + clearLastParentBeforePip(); deferRelaunchUntilPaused = false; frozenBeforeDestroy = false; @@ -5863,6 +5894,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(); } } @@ -6679,26 +6716,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); } @@ -7022,11 +7059,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and back which can cause visible issues (see b/184078928). final int parentWindowingMode = newParentConfiguration.windowConfiguration.getWindowingMode(); + final boolean isFixedOrientationLetterboxAllowed = + isSplitScreenWindowingMode(parentWindowingMode) + || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW + || parentWindowingMode == WINDOWING_MODE_FULLSCREEN; // TODO(b/181207944): Consider removing the if condition and always run // resolveFixedOrientationConfiguration() since this should be applied for all cases. - if (isSplitScreenWindowingMode(parentWindowingMode) - || parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW - || parentWindowingMode == WINDOWING_MODE_FULLSCREEN) { + if (isFixedOrientationLetterboxAllowed) { resolveFixedOrientationConfiguration(newParentConfiguration); } @@ -7047,12 +7086,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveFullscreenConfiguration(newParentConfiguration); } + if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null + // In fullscreen, can be letterboxed for aspect ratio. + || !inMultiWindowMode()) { + updateResolvedBoundsHorizontalPosition(newParentConfiguration); + } + if (mVisibleRequested) { updateCompatDisplayInsets(); } - // TODO(b/175212232): Consolidate position logic from each "resolve" method above here. - // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. @@ -7083,6 +7126,47 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + + /** + * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity + * requested in the config or via an ADB command. For more context see {@link + * WindowManagerService#getLetterboxHorizontalPositionMultiplier}. + */ + private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) { + final Configuration resolvedConfig = getResolvedOverrideConfiguration(); + final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + final Rect screenResolvedBounds = + mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; + final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); + final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); + if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) { + return; + } + + int offsetX = 0; + if (screenResolvedBounds.width() >= parentAppBounds.width()) { + // If resolved bounds overlap with insets, center within app bounds. + offsetX = getHorizontalCenterOffset( + parentAppBounds.width(), screenResolvedBounds.width()); + } else { + float positionMultiplier = mWmService.getLetterboxHorizontalPositionMultiplier(); + positionMultiplier = + (positionMultiplier < 0.0f || positionMultiplier > 1.0f) + // Default to central position if invalid value is provided. + ? 0.5f : positionMultiplier; + offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width()) + * positionMultiplier); + } + + if (mSizeCompatBounds != null) { + mSizeCompatBounds.offset(offsetX, 0 /* offsetY */); + final int dx = mSizeCompatBounds.left - resolvedBounds.left; + offsetBounds(resolvedConfig, dx, 0 /* offsetY */); + } else { + offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */); + } + } + /** * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed * orientation then aspect ratio restrictions are also already respected. @@ -7160,7 +7244,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height); } else { final int width = (int) Math.rint(parentHeight / aspect); - final int left = parentBounds.centerX() - width / 2; + final Rect parentAppBounds = newParentConfig.windowConfiguration.getAppBounds(); + final int left = width <= parentAppBounds.width() + // Avoid overlapping with the horizontal decor area when possible. + ? parentAppBounds.left : parentBounds.centerX() - width / 2; resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); } @@ -7212,12 +7299,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, getFixedRotationTransformDisplayInfo()); } - if (needToBeCentered) { - // Offset to center relative to parent's app bounds. - final int offsetX = getHorizontalCenterOffset( - parentAppBounds.width(), resolvedBounds.width()); - offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */); - } } /** @@ -7315,8 +7396,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); - // Calculates the scale and offset to horizontal center the size compatibility bounds into - // the region which is available to application. + // Calculates the scale the size compatibility bounds into the region which is available + // to application. final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); @@ -7324,8 +7405,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); - final int screenTopInset = containerAppBounds.top - containerBounds.top; - final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top; + final int containerTopInset = containerAppBounds.top - containerBounds.top; + final boolean topNotAligned = + containerTopInset != resolvedAppBounds.top - resolvedBounds.top; if (mSizeCompatScale != 1f || topNotAligned) { if (mSizeCompatBounds == null) { mSizeCompatBounds = new Rect(); @@ -7334,18 +7416,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mSizeCompatBounds.offsetTo(0, 0); mSizeCompatBounds.scale(mSizeCompatScale); // The insets are included in height, e.g. the area of real cutout shouldn't be scaled. - mSizeCompatBounds.bottom += screenTopInset; + mSizeCompatBounds.bottom += containerTopInset; } else { mSizeCompatBounds = null; } - // Center horizontally in parent (app bounds) and align to top of parent (bounds) - // - this is a UX choice. - final int offsetX = getHorizontalCenterOffset( - (int) viewportW, (int) (contentW * mSizeCompatScale)); + // Align to top of parent (bounds) - this is a UX choice and exclude the horizontal decor + // if needed. Horizontal position is adjusted in updateResolvedBoundsHorizontalPosition. // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = (fillContainer - ? containerBounds.left : containerAppBounds.left) + offsetX; + final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; final int screenPosY = containerBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 6631a3e61e77..4bb48b0bdbe8 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1415,6 +1415,9 @@ class RecentTasks { return true; } + // The given task if always treated as in visible range if it is the origin of pinned task. + if (task.mChildPipActivity != null) return true; + if (mMaxNumVisibleTasks >= 0) { // Always keep up to the max number of recent tasks, but return false afterwards return numVisibleTasks <= mMaxNumVisibleTasks; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 52551ec54b32..b9fc8b1222ee 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2124,6 +2124,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> .setDeferTaskAppear(true) .setHasBeenVisible(true) .build(); + // Establish bi-directional link between the original and pinned task. + r.setLastParentBeforePip(); // It's possible the task entering PIP is in freeform, so save the last // non-fullscreen bounds. Then when this new PIP task exits PIP, it can restore // to its previous freeform bounds. diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 95a4f69edd57..fb66c0493025 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -36,9 +36,11 @@ import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER; import android.animation.ArgbEvaluator; import android.content.Context; import android.graphics.Color; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Trace; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -210,9 +212,9 @@ class ScreenRotationAnimation { String name = "RotationLayer"; mScreenshotLayer = displayContent.makeOverlay() .setName(name) - .setBufferSize(mWidth, mHeight) .setSecure(isSecure) .setCallsite("ScreenRotationAnimation") + .setBLASTLayer() .build(); // This is the way to tell the input system to exclude this surface from occlusion // detection since we don't have a window for it. We do this because this window is @@ -225,32 +227,29 @@ class ScreenRotationAnimation { .setCallsite("ScreenRotationAnimation") .build(); - final Surface surface = mService.mSurfaceFactory.get(); - // In case display bounds change, screenshot buffer and surface may mismatch so - // set a scaling mode. - surface.copyFrom(mScreenshotLayer); - surface.setScalingMode(Surface.SCALING_MODE_SCALE_TO_WINDOW); - + HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ScreenRotationAnimation#getMedianBorderLuma"); - mStartLuma = RotationAnimationUtils.getMedianBorderLuma( - screenshotBuffer.getHardwareBuffer(), screenshotBuffer.getColorSpace()); + mStartLuma = RotationAnimationUtils.getMedianBorderLuma(hardwareBuffer, + screenshotBuffer.getColorSpace()); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - try { - surface.attachAndQueueBufferWithColorSpace(screenshotBuffer.getHardwareBuffer(), - screenshotBuffer.getColorSpace()); - } catch (RuntimeException e) { - Slog.w(TAG, "Failed to attach screenshot - " + e.getMessage()); - } + + GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer( + screenshotBuffer.getHardwareBuffer()); + // Scale the layer to the display size. + float dsdx = (float) mWidth / hardwareBuffer.getWidth(); + float dsdy = (float) mHeight / hardwareBuffer.getHeight(); t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE); t.reparent(mBackColorSurface, displayContent.getSurfaceControl()); t.setLayer(mBackColorSurface, -1); t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma}); t.setAlpha(mBackColorSurface, 1); + t.setBuffer(mScreenshotLayer, buffer); + t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace()); + t.setMatrix(mScreenshotLayer, dsdx, 0, 0, dsdy); t.show(mScreenshotLayer); t.show(mBackColorSurface); - surface.destroy(); } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b82a30847c8d..c6cd5606770e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -640,9 +640,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } - void windowAddedLocked(String packageName) { - mPackageName = packageName; - mRelayoutTag = "relayoutWindow: " + mPackageName; + void windowAddedLocked() { + if (mPackageName == null) { + final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid); + if (wpc != null) { + mPackageName = wpc.mInfo.packageName; + mRelayoutTag = "relayoutWindow: " + mPackageName; + } else { + Slog.e(TAG_WM, "Unknown process pid=" + mPid); + } + } if (mSurfaceSession == null) { if (DEBUG) { Slog.v(TAG_WM, "First window added to " + this + ", creating SurfaceSession"); diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 140ae3e7d756..603bfd153481 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -58,10 +58,12 @@ public class StartingSurfaceController { overrideConfig, displayId); } - final Task task = activity.getTask(); - if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, - activity.token, theme)) { - return new ShellStartingSurface(task); + synchronized (mService.mGlobalLock) { + final Task task = activity.getTask(); + if (task != null && mService.mAtmService.mTaskOrganizerController.addStartingWindow( + task, activity.token, theme)) { + return new ShellStartingSurface(task); + } } return null; } @@ -124,14 +126,13 @@ public class StartingSurfaceController { activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation( topFullscreenActivity, false /* checkOpening */); } + if (DEBUG_ENABLE_SHELL_DRAWER) { + mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, + activity.token, 0 /* launchTheme */); + return new ShellStartingSurface(task); + } } - if (!DEBUG_ENABLE_SHELL_DRAWER) { - return mService.mTaskSnapshotController - .createStartingSurface(activity, taskSnapshot); - } - mService.mAtmService.mTaskOrganizerController.addStartingWindow(task, activity.token, - 0 /* launchTheme */); - return new ShellStartingSurface(task); + return mService.mTaskSnapshotController.createStartingSurface(activity, taskSnapshot); } @@ -144,8 +145,9 @@ public class StartingSurfaceController { @Override public void remove(boolean animate) { - mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, - animate); + synchronized (mService.mGlobalLock) { + mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask, animate); + } } } } diff --git a/services/core/java/com/android/server/wm/SurfaceFreezer.java b/services/core/java/com/android/server/wm/SurfaceFreezer.java index 6f434e05c800..e0a791e118bb 100644 --- a/services/core/java/com/android/server/wm/SurfaceFreezer.java +++ b/services/core/java/com/android/server/wm/SurfaceFreezer.java @@ -22,6 +22,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_SCREEN_ROTATI import android.annotation.NonNull; import android.annotation.Nullable; +import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.HardwareBuffer; @@ -153,29 +154,24 @@ class SurfaceFreezer { */ Snapshot(Supplier<Surface> surfaceFactory, SurfaceControl.Transaction t, SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer, SurfaceControl parent) { - Surface drawSurface = surfaceFactory.get(); // We can't use a delegating constructor since we need to // reference this::onAnimationFinished - HardwareBuffer hardwareBuffer = screenshotBuffer.getHardwareBuffer(); - final int width = hardwareBuffer.getWidth(); - final int height = hardwareBuffer.getHeight(); + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( + screenshotBuffer.getHardwareBuffer()); mSurfaceControl = mAnimatable.makeAnimationLeash() .setName("snapshot anim: " + mAnimatable.toString()) - .setBufferSize(width, height) .setFormat(PixelFormat.TRANSLUCENT) .setParent(parent) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("SurfaceFreezer.Snapshot") + .setBLASTLayer() .build(); ProtoLog.i(WM_SHOW_TRANSACTIONS, " THUMBNAIL %s: CREATE", mSurfaceControl); - // Transfer the thumbnail to the surface - drawSurface.copyFrom(mSurfaceControl); - drawSurface.attachAndQueueBufferWithColorSpace(hardwareBuffer, - screenshotBuffer.getColorSpace()); - drawSurface.release(); + t.setBuffer(mSurfaceControl, graphicBuffer); + t.setColorSpace(mSurfaceControl, screenshotBuffer.getColorSpace()); t.show(mSurfaceControl); // We parent the thumbnail to the container, and just place it on top of anything else diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 86904991616f..858d9f366a83 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -131,6 +131,7 @@ import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; import static com.android.server.wm.TaskProto.DISPLAY_ID; import static com.android.server.wm.TaskProto.FILLS_PARENT; +import static com.android.server.wm.TaskProto.HAS_CHILD_PIP_ACTIVITY; import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS; import static com.android.server.wm.TaskProto.MIN_HEIGHT; import static com.android.server.wm.TaskProto.MIN_WIDTH; @@ -836,6 +837,14 @@ class Task extends WindowContainer<WindowContainer> { // The task will be removed when TaskOrganizer, which is managing the task, is destroyed. boolean mRemoveWithTaskOrganizer; + /** + * Reference to the pinned activity that is logically parented to this task, ie. + * the previous top activity within this task is put into pinned mode. + * This always gets cleared in pair with the ActivityRecord-to-Task link as seen in + * {@link ActivityRecord#clearLastParentBeforePip()}. + */ + ActivityRecord mChildPipActivity; + private Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, ComponentName _origActivity, boolean _rootWasReset, @@ -1841,6 +1850,10 @@ class Task extends WindowContainer<WindowContainer> { /** Completely remove all activities associated with an existing task. */ void performClearTask(String reason) { + // The original task is to be removed, try remove also the pinned task. + if (mChildPipActivity != null && mChildPipActivity.getTask() != null) { + mTaskSupervisor.removeRootTask(mChildPipActivity.getTask()); + } // Broken down into to cases to avoid object create due to capturing mStack. if (getRootTask() == null) { forAllActivities((r) -> { @@ -4449,6 +4462,7 @@ class Task extends WindowContainer<WindowContainer> { } pw.print(prefix); pw.print("taskId=" + mTaskId); pw.println(" rootTaskId=" + getRootTaskId()); + pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null)); pw.print(prefix); pw.print("mHasBeenVisible="); pw.println(getHasBeenVisible()); pw.print(prefix); pw.print("mResizeMode="); pw.print(ActivityInfo.resizeModeToString(mResizeMode)); @@ -5328,7 +5342,6 @@ class Task extends WindowContainer<WindowContainer> { return; } final int currentMode = getWindowingMode(); - final int currentOverrideMode = getRequestedOverrideWindowingMode(); final Task topTask = getTopMostTask(); int windowingMode = preferredWindowingMode; @@ -5397,9 +5410,26 @@ class Task extends WindowContainer<WindowContainer> { mTaskSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); - // setWindowingMode triggers an onConfigurationChanged cascade which can result in a - // different resolved windowing mode (usually when preferredWindowingMode is UNDEFINED). - windowingMode = getWindowingMode(); + + // Try reparent pinned activity back to its original task after onConfigurationChanged + // cascade finishes. This is done on Task level instead of + // {@link ActivityRecord#onConfigurationChanged(Configuration)} since when we exit PiP, + // we set final windowing mode on the ActivityRecord first and then on its Task when + // the exit PiP transition finishes. Meanwhile, the exit transition is always + // performed on its original task, reparent immediately in ActivityRecord breaks it. + if (currentMode == WINDOWING_MODE_PINNED) { + if (topActivity != null && topActivity.getLastParentBeforePip() != null) { + // Do not reparent if the pinned task is in removal, indicated by the + // force hidden flag. + if (!isForceHidden()) { + final Task lastParentBeforePip = topActivity.getLastParentBeforePip(); + topActivity.reparent(lastParentBeforePip, + lastParentBeforePip.getChildCount() /* top */, + "movePinnedActivityToOriginalTask"); + lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask"); + } + } + } if (creating) { // Nothing else to do if we don't have a window container yet. E.g. call from ctor. @@ -6279,6 +6309,8 @@ class Task extends WindowContainer<WindowContainer> { // Launching this app's activity, make sure the app is no longer // considered stopped. try { + mTaskSupervisor.getActivityMetricsLogger() + .notifyBeforePackageUnstopped(next.packageName); mAtmService.getPackageManager().setPackageStoppedState( next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */ } catch (RemoteException e1) { @@ -7530,7 +7562,11 @@ class Task extends WindowContainer<WindowContainer> { final Task task = getBottomMostTask(); setWindowingMode(WINDOWING_MODE_UNDEFINED); - getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + // Task could have been removed from the hierarchy due to windowing mode change + // where its only child is reparented back to their original parent task. + if (isAttached()) { + getDisplayArea().positionChildAt(POSITION_TOP, this, false /* includingParents */); + } mTaskSupervisor.scheduleUpdatePictureInPictureModeIfNeeded(task, this); }); @@ -7831,6 +7867,7 @@ class Task extends WindowContainer<WindowContainer> { proto.write(CREATED_BY_ORGANIZER, mCreatedByOrganizer); proto.write(AFFINITY, affinity); + proto.write(HAS_CHILD_PIP_ACTIVITY, mChildPipActivity != null); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java index dff621c5871a..625cff340912 100644 --- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java @@ -439,6 +439,13 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier { taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea(); } + // Re-route to default display if the home activity doesn't support multi-display + if (taskDisplayArea != null && activityRecord.isActivityTypeHome() + && !mSupervisor.mRootWindowContainer.canStartHomeOnDisplayArea(activityRecord.info, + taskDisplayArea, false /* allowInstrumenting */)) { + taskDisplayArea = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea(); + } + return (taskDisplayArea != null) ? taskDisplayArea : getFallbackDisplayAreaForActivity(activityRecord, request); 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..0c9473a658af 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; @@ -1024,6 +1024,10 @@ public class WindowManagerService extends IWindowManager.Stub // Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. private float mLetterboxBackgroundWallpaperDarkScrimAlpha; + // horizontal position of a center of the letterboxed app window. 0 corresponds to the left + // side of the screen and 1.0 to the right side. + private float mLetterboxHorizontalPositionMultiplier; + final InputManagerService mInputManager; final DisplayManagerInternal mDisplayManagerInternal; final DisplayManager mDisplayManager; @@ -1265,6 +1269,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius); mLetterboxBackgroundWallpaperDarkScrimAlpha = context.getResources().getFloat( com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha); + mLetterboxHorizontalPositionMultiplier = context.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -1418,6 +1424,8 @@ 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 +1748,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) { @@ -4053,6 +4064,38 @@ public class WindowManagerService extends IWindowManager.Stub return mLetterboxBackgroundWallpaperBlurRadius; } + /* + * Gets horizontal position of a center of the letterboxed app window specified + * in {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} + * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the + * right side. + * + * <p>This value can be outside of [0, 1] range so clients need to check and default to the + * central position (0.5). + */ + float getLetterboxHorizontalPositionMultiplier() { + return mLetterboxHorizontalPositionMultiplier; + } + + /** + * Overrides horizontal position of a center of the letterboxed app window. If given value < 0 + * or > 1, then it and a value of {@link + * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and + * central position (0.5) is used. + */ + void setLetterboxHorizontalPositionMultiplier(float multiplier) { + mLetterboxHorizontalPositionMultiplier = multiplier; + } + + /** + * Resets horizontal position of a center of the letterboxed app window to {@link + * com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}. + */ + void resetLetterboxHorizontalPositionMultiplier() { + mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier); + } + @Override public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) { if (!checkCallingPermission( diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index 5942f34048e7..68257d4adb7f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -143,6 +143,10 @@ public class WindowManagerShellCommand extends ShellCommand { return runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); case "get-letterbox-background-wallpaper-dark-scrim-alpha": return runGetLetterboxBackgroundWallpaperDarkScrimAlpha(pw); + case "set-letterbox-horizontal-position-multiplier": + return runSeLetterboxHorizontalPositionMultiplier(pw); + case "get-letterbox-horizontal-position-multiplier": + return runGetLetterboxHorizontalPositionMultiplier(pw); case "set-sandbox-display-apis": return runSandboxDisplayApis(pw); case "reset": @@ -846,6 +850,43 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSeLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException { + final float multiplier; + try { + String arg = getNextArgRequired(); + if ("reset".equals(arg)) { + synchronized (mInternal.mGlobalLock) { + mInternal.resetLetterboxHorizontalPositionMultiplier(); + } + return 0; + } + multiplier = Float.parseFloat(arg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: bad multiplier format " + e); + return -1; + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Error: 'reset' or multiplier should be provided as an argument " + e); + return -1; + } + synchronized (mInternal.mGlobalLock) { + mInternal.setLetterboxHorizontalPositionMultiplier(multiplier); + } + return 0; + } + + private int runGetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException { + synchronized (mInternal.mGlobalLock) { + final float multiplier = mInternal.getLetterboxHorizontalPositionMultiplier(); + if (multiplier < 0) { + pw.println("Letterbox horizontal position multiplier is not set"); + } else { + pw.println("Letterbox horizontal position multiplier is " + multiplier); + } + } + return 0; + } + private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); @@ -888,6 +929,9 @@ public class WindowManagerShellCommand extends ShellCommand { // set-letterbox-background-wallpaper-dark-scrim-alpha mInternal.resetLetterboxBackgroundWallpaperDarkScrimAlpha(); + // set-letterbox-horizontal-position-multiplier + mInternal.resetLetterboxHorizontalPositionMultiplier(); + // set-sandbox-display-apis mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true); @@ -954,6 +998,11 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" letterbox background. If alpha < 0 or >= 1 both it and"); pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored and "); pw.println(" 0.0 (transparent) is used instead."); + pw.println(" set-letterbox-horizontal-position-multiplier [reset|multiplier]"); + pw.println(" get-letterbox-horizontal-position-multiplier"); + pw.println(" horizontal position of a center of a letterboxed app. If it < 0 or > 1"); + pw.println(" then both it and R.dimen.config_letterboxHorizontalPositionMultiplier"); + pw.println(" are ignored and central position (0.5) is used."); pw.println(" set-sandbox-display-apis [true|1|false|0]"); pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect "); pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or"); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 2e37fee1fb1c..9382b8eed0b8 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -36,6 +36,7 @@ import android.annotation.Nullable; import android.app.WindowConfiguration; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.graphics.GraphicBuffer; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Binder; @@ -45,7 +46,6 @@ import android.os.Parcel; import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; -import android.view.Surface; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; @@ -766,18 +766,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return false; } + GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer( + buffer.getHardwareBuffer()); SurfaceControl screenshot = mService.mWindowManager.mSurfaceControlFactory.apply(null) .setName(wc.getName() + " - Organizer Screenshot") - .setBufferSize(bounds.width(), bounds.height()) .setFormat(PixelFormat.TRANSLUCENT) .setParent(wc.getParentSurfaceControl()) + .setSecure(buffer.containsSecureLayers()) .setCallsite("WindowOrganizerController.takeScreenshot") + .setBLASTLayer() .build(); - Surface surface = new Surface(); - surface.copyFrom(screenshot); - surface.attachAndQueueBufferWithColorSpace(buffer.getHardwareBuffer(), null); - surface.release(); + SurfaceControl.Transaction transaction = mService.mWindowManager.mTransactionFactory.get(); + transaction.setBuffer(screenshot, graphicBuffer); + transaction.setColorSpace(screenshot, buffer.getColorSpace()); + transaction.apply(); outSurfaceControl.copyFrom(screenshot, "WindowOrganizerController.takeScreenshot"); return true; diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 46d923b0cfb0..1a5042ffd56d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -1140,7 +1140,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void attach() { if (DEBUG) Slog.v(TAG, "Attaching " + this + " token=" + mToken); - mSession.windowAddedLocked(mAttrs.packageName); + mSession.windowAddedLocked(); } /** diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index ddcb2bf7be22..6283b4e56402 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -96,6 +96,7 @@ import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_REMOVE_N import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED; import static android.app.admin.DevicePolicyManager.PROVISIONING_RESULT_STARTING_PROFILE_FAILED; +import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED; import static android.app.admin.DevicePolicyManager.WIPE_EUICC; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -8517,20 +8518,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 @@ -8739,7 +8736,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final CallerIdentity caller = getCallerIdentity(); if (userHandle != mOwners.getDeviceOwnerUserId() && !mOwners.hasProfileOwner(userHandle) - && getManagedUserId(userHandle) == -1) { + && getManagedUserId(userHandle) == -1 + && newState != STATE_USER_UNMANAGED) { // No managed device, user or profile, so setting provisioning state makes no sense. throw new IllegalStateException("Not allowed to change provisioning state unless a " + "device or profile owner is set."); @@ -8802,6 +8800,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { case DevicePolicyManager.STATE_USER_SETUP_FINALIZED: // Cannot transition out of finalized. break; + case DevicePolicyManager.STATE_USER_PROFILE_FINALIZED: + // Should only move to an unmanaged state after removing the work profile. + if (newState == DevicePolicyManager.STATE_USER_UNMANAGED) { + return; + } + break; } // Didn't meet any of the accepted state transition checks above, throw appropriate error. 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_tile_layout_side_labels.xml b/services/tests/PackageManagerServiceTests/host/test-apps/Overlay/AndroidManifest.xml index efa240362f67..21e4432b299f 100644 --- a/packages/SystemUI/res/layout/qs_paged_tile_layout_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. @@ -15,13 +14,9 @@ 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"> + <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/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.xml new file mode 100644 index 000000000000..6038cb1ec20c --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/OverlayTarget/AndroidManifest.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. +--> + +<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/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml index e4b650cad055..17a5dccb57da 100644 --- a/services/tests/mockingservicestests/AndroidManifest.xml +++ b/services/tests/mockingservicestests/AndroidManifest.xml @@ -28,6 +28,8 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS"/> <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/> + <uses-permission + android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/> <!-- needed by MasterClearReceiverTest to display a system dialog --> <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/> diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index edfc21d47767..d55bbd1d8e45 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -468,8 +468,9 @@ public class AlarmManagerServiceTest { TEST_CALLING_UID); } - private void setPrioritizedAlarm(int type, long triggerTime, IAlarmListener listener) { - mService.setImpl(type, triggerTime, WINDOW_EXACT, 0, null, listener, "test", + private void setPrioritizedAlarm(int type, long triggerTime, long windowLength, + IAlarmListener listener) { + mService.setImpl(type, triggerTime, windowLength, 0, null, listener, "test", FLAG_STANDALONE | FLAG_PRIORITIZE, null, null, TEST_CALLING_UID, TEST_CALLING_PACKAGE, null); } @@ -1685,7 +1686,7 @@ public class AlarmManagerServiceTest { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - new IAlarmListener.Stub() { + 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { @@ -1720,7 +1721,7 @@ public class AlarmManagerServiceTest { final int numAlarms = 10; for (int i = 0; i < numAlarms; i++) { setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + i, - new IAlarmListener.Stub() { + 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { @@ -1738,12 +1739,12 @@ public class AlarmManagerServiceTest { } assertEquals(numAlarms, alarmsFired.get()); - setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, new IAlarmListener.Stub() { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 3, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } }); - setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, new IAlarmListener.Stub() { + setPrioritizedAlarm(ELAPSED_REALTIME_WAKEUP, idleUntil - 2, 0, new IAlarmListener.Stub() { @Override public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { } @@ -2223,7 +2224,11 @@ 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 +2244,48 @@ 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 minWindowPriorityAlarm() { + doReturn(true).when( + () -> CompatChanges.isChangeEnabled( + eq(AlarmManager.ENFORCE_MINIMUM_WINDOW_ON_INEXACT_ALARMS), + anyString(), any(UserHandle.class))); + final long minWindow = 73; + setDeviceConfigLong(KEY_MIN_WINDOW, minWindow); + + // 0 is WINDOW_EXACT and < 0 is WINDOW_HEURISTIC. + for (int window = 1; window <= minWindow; window++) { + setPrioritizedAlarm(ELAPSED_REALTIME, 0, window, new IAlarmListener.Stub() { + @Override + public void doAlarm(IAlarmCompleteListener callback) throws RemoteException { + } + }); + assertEquals(1, mService.mAlarmStore.size()); + final Alarm a = mService.mAlarmStore.remove(unused -> true).get(0); + assertEquals(window, a.windowLength); + } + } + + @Test public void denyListPackagesAdded() { mService.mConstants.EXACT_ALARM_DENY_LIST = new ArraySet<>(new String[]{"p1", "p2", "p3"}); setDeviceConfigString(KEY_EXACT_ALARM_DENY_LIST, "p2,p4,p5"); diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index edc0d468dbe1..a8d8a90e8935 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -16,51 +16,66 @@ package com.android.server.app; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; import android.Manifest; import android.app.GameManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest @Presubmit public class GameManagerServiceTests { - + @Mock MockContext mMockContext; private static final String TAG = "GameServiceTests"; private static final String PACKAGE_NAME_INVALID = "com.android.app"; private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; + private MockitoSession mMockingSession; + private String mPackageName; + @Mock + private PackageManager mMockPackageManager; + // Stolen from ConnectivityServiceTest.MockContext - static class MockContext extends ContextWrapper { + class MockContext extends ContextWrapper { private static final String TAG = "MockContext"; // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); - @Mock - private final MockPackageManager mMockPackageManager; - MockContext(Context base) { super(base); - mMockPackageManager = new MockPackageManager(); } /** @@ -112,15 +127,31 @@ public class GameManagerServiceTests { } } - @Mock - private MockContext mMockContext; - - private String mPackageName; - @Before public void setUp() throws Exception { + mMockingSession = mockitoSession() + .initMocks(this) + .mockStatic(DeviceConfig.class) + .strictness(Strictness.WARN) + .startMocking(); mMockContext = new MockContext(InstrumentationRegistry.getContext()); mPackageName = mMockContext.getPackageName(); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.category = ApplicationInfo.CATEGORY_GAME; + final PackageInfo pi = new PackageInfo(); + pi.packageName = mPackageName; + final List<PackageInfo> packages = new ArrayList<>(); + packages.add(pi); + when(mMockPackageManager.getInstalledPackages(anyInt())).thenReturn(packages); + when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())) + .thenReturn(applicationInfo); + } + + @After + public void tearDown() throws Exception { + if (mMockingSession != null) { + mMockingSession.finishMocking(); + } } private void mockModifyGameModeGranted() { @@ -133,6 +164,60 @@ public class GameManagerServiceTests { PackageManager.PERMISSION_DENIED); } + private void mockDeviceConfigDefault() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, "").build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigNone() { + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigPerformance() { + String configString = "mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigBattery() { + String configString = "mode=3,downscaleFactor=0.7"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigAll() { + String configString = "mode=3,downscaleFactor=0.7:mode=2,downscaleFactor=0.5"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigInvalid() { + String configString = "mode=2,downscaleFactor=0.55"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + + private void mockDeviceConfigMalformed() { + String configString = "adsljckv=nin3rn9hn1231245:8795tq=21ewuydg"; + DeviceConfig.Properties properties = new DeviceConfig.Properties.Builder( + DeviceConfig.NAMESPACE_GAME_OVERLAY).setString(mPackageName, configString).build(); + when(DeviceConfig.getProperties(anyString(), anyString())) + .thenReturn(properties); + } + /** * By default game mode is not supported. */ @@ -190,7 +275,6 @@ public class GameManagerServiceTests { public void testGetGameModeInvalidPackageName() { GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); - try { assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_INVALID, @@ -268,4 +352,137 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(mPackageName, USER_ID_2)); } + + /** + * Phonesky device config exists, but is only propagating the default value. + */ + @Test + public void testDeviceConfigDefault() { + mockDeviceConfigDefault(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config does not exists. + */ + @Test + public void testDeviceConfigNone() { + mockDeviceConfigNone(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config for performance mode exists and is valid. + */ + @Test + public void testDeviceConfigPerformance() { + mockDeviceConfigPerformance(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config for battery mode exists and is valid. + */ + @Test + public void testDeviceConfigBattery() { + mockDeviceConfigBattery(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } + } + assertEquals(modes.length, 1); + assertTrue(batteryModeExists); + } + + /** + * Phonesky device configs for both battery and performance modes exists and are valid. + */ + @Test + public void testDeviceConfigAll() { + mockDeviceConfigAll(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + boolean batteryModeExists = false; + boolean perfModeExists = false; + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + for (int mode : modes) { + if (mode == GameManager.GAME_MODE_BATTERY) { + batteryModeExists = true; + } else if (mode == GameManager.GAME_MODE_PERFORMANCE) { + perfModeExists = true; + } + } + assertTrue(batteryModeExists); + assertTrue(perfModeExists); + } + + /** + * Phonesky device config contains values that parse correctly but are not valid in game mode. + */ + @Test + public void testDeviceConfigInvalid() { + mockDeviceConfigInvalid(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } + + /** + * Phonesky device config is garbage. + */ + @Test + public void testDeviceConfigMalformed() { + mockDeviceConfigMalformed(); + mockModifyGameModeGranted(); + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + gameManagerService.loadDeviceConfigLocked(); + + int[] modes = gameManagerService.getAvailableGameModes(mPackageName); + assertEquals(modes.length, 1); + assertEquals(modes[0], GameManager.GAME_MODE_UNSUPPORTED); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java index 924ad7f3f5a1..76f7e80a3412 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -35,7 +35,6 @@ import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; -import android.util.ArrayMap; import android.util.LongSparseArray; import androidx.test.InstrumentationRegistry; @@ -68,7 +67,6 @@ public class BlobStoreManagerServiceTest { private File mBlobsDir; private LongSparseArray<BlobStoreSession> mUserSessions; - private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs; private static final String TEST_PKG1 = "com.example1"; private static final String TEST_PKG2 = "com.example2"; @@ -99,10 +97,8 @@ public class BlobStoreManagerServiceTest { mHandler = new TestHandler(Looper.getMainLooper()); mService = new BlobStoreManagerService(mContext, new TestInjector()); mUserSessions = new LongSparseArray<>(); - mUserBlobs = new ArrayMap<>(); mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); - mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId()); } @After @@ -147,7 +143,7 @@ public class BlobStoreManagerServiceTest { final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1, true /* hasLeases */); doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1); - mUserBlobs.put(blobHandle1, blobMetadata1); + addBlob(blobHandle1, blobMetadata1); final long blobId2 = 347; final File blobFile2 = mock(File.class); @@ -156,7 +152,7 @@ public class BlobStoreManagerServiceTest { final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2, false /* hasLeases */); doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1); - mUserBlobs.put(blobHandle2, blobMetadata2); + addBlob(blobHandle2, blobMetadata2); final long blobId3 = 49875; final File blobFile3 = mock(File.class); @@ -165,7 +161,7 @@ public class BlobStoreManagerServiceTest { final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3, true /* hasLeases */); doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1); - mUserBlobs.put(blobHandle3, blobMetadata3); + addBlob(blobHandle3, blobMetadata3); mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4, blobId1, blobId2, blobId3); @@ -197,10 +193,10 @@ public class BlobStoreManagerServiceTest { verify(blobMetadata2).destroy(); verify(blobMetadata3).destroy(); - assertThat(mUserBlobs.size()).isEqualTo(1); - assertThat(mUserBlobs.get(blobHandle1)).isNotNull(); - assertThat(mUserBlobs.get(blobHandle2)).isNull(); - assertThat(mUserBlobs.get(blobHandle3)).isNull(); + assertThat(mService.getBlobsCountForTest()).isEqualTo(1); + assertThat(mService.getBlobForTest(blobHandle1)).isNotNull(); + assertThat(mService.getBlobForTest(blobHandle2)).isNull(); + assertThat(mService.getBlobForTest(blobHandle3)).isNull(); assertThat(mService.getActiveIdsForTest()).containsExactly( sessionId2, sessionId3, blobId1); @@ -293,7 +289,7 @@ public class BlobStoreManagerServiceTest { "label1", System.currentTimeMillis() - 2000, "tag1"); final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1, true /* hasLeases */); - mUserBlobs.put(blobHandle1, blobMetadata1); + addBlob(blobHandle1, blobMetadata1); final long blobId2 = 78974; final File blobFile2 = mock(File.class); @@ -301,7 +297,7 @@ public class BlobStoreManagerServiceTest { "label2", System.currentTimeMillis() + 30000, "tag2"); final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2, true /* hasLeases */); - mUserBlobs.put(blobHandle2, blobMetadata2); + addBlob(blobHandle2, blobMetadata2); final long blobId3 = 97; final File blobFile3 = mock(File.class); @@ -309,7 +305,7 @@ public class BlobStoreManagerServiceTest { "label3", System.currentTimeMillis() + 4400000, "tag3"); final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3, false /* hasLeases */); - mUserBlobs.put(blobHandle3, blobMetadata3); + addBlob(blobHandle3, blobMetadata3); mService.addActiveIdsForTest(blobId1, blobId2, blobId3); @@ -321,8 +317,8 @@ public class BlobStoreManagerServiceTest { verify(blobMetadata2, never()).destroy(); verify(blobMetadata3).destroy(); - assertThat(mUserBlobs.size()).isEqualTo(1); - assertThat(mUserBlobs.get(blobHandle2)).isNotNull(); + assertThat(mService.getBlobsCountForTest()).isEqualTo(1); + assertThat(mService.getBlobForTest(blobHandle2)).isNotNull(); assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2); assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3); @@ -336,21 +332,21 @@ public class BlobStoreManagerServiceTest { doReturn(size1).when(blobMetadata1).getSize(); doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1); doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2); - mUserBlobs.put(mock(BlobHandle.class), blobMetadata1); + addBlob(mock(BlobHandle.class), blobMetadata1); final BlobMetadata blobMetadata2 = mock(BlobMetadata.class); final long size2 = 89475; doReturn(size2).when(blobMetadata2).getSize(); doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1); doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2); - mUserBlobs.put(mock(BlobHandle.class), blobMetadata2); + addBlob(mock(BlobHandle.class), blobMetadata2); final BlobMetadata blobMetadata3 = mock(BlobMetadata.class); final long size3 = 328732; doReturn(size3).when(blobMetadata3).getSize(); doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1); doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2); - mUserBlobs.put(mock(BlobHandle.class), blobMetadata3); + addBlob(mock(BlobHandle.class), blobMetadata3); // Verify usage is calculated correctly assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1)) @@ -388,6 +384,11 @@ public class BlobStoreManagerServiceTest { return blobMetadata; } + private void addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata) { + doReturn(blobHandle).when(blobMetadata).getBlobHandle(); + mService.addBlobLocked(blobMetadata); + } + private class TestHandler extends Handler { TestHandler(Looper looper) { super(looper); diff --git a/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/servicestests/src/com/android/server/am/BroadcastRecordTest.java index 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..1b42dfa0712e 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 @@ -3169,6 +3179,16 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + public void testSetUserProvisioningState_profileFinalized_canTransitionToUserUnmanaged() + throws Exception { + setupProfileOwner(); + + exerciseUserProvisioningTransitions(CALLER_USER_HANDLE, + DevicePolicyManager.STATE_USER_PROFILE_FINALIZED, + DevicePolicyManager.STATE_USER_UNMANAGED); + } + + @Test public void testSetUserProvisioningState_illegalTransitionToAnotherInProgressState() throws Exception { setupProfileOwner(); @@ -5156,7 +5176,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 +5204,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 +5259,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 +6382,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 +6402,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 +7081,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/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 809b6d561362..182848b4f628 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -123,6 +123,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (long i = cal.getTimeInMillis(); i >= 5; i--) { File file = mock(File.class); when(file.getName()).thenReturn(String.valueOf(i)); + when(file.getAbsolutePath()).thenReturn(String.valueOf(i)); AtomicFile af = new AtomicFile(file); expectedFiles.add(af); mDataBase.mHistoryFiles.addLast(af); @@ -133,6 +134,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (int i = 5; i >= 0; i--) { File file = mock(File.class); when(file.getName()).thenReturn(String.valueOf(cal.getTimeInMillis() - i)); + when(file.getAbsolutePath()).thenReturn(String.valueOf(cal.getTimeInMillis() - i)); AtomicFile af = new AtomicFile(file); mDataBase.mHistoryFiles.addLast(af); } @@ -158,6 +160,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { for (long i = cal.getTimeInMillis(); i >= 5; i--) { File file = mock(File.class); when(file.getName()).thenReturn(i + ".bak"); + when(file.getAbsolutePath()).thenReturn(i + ".bak"); AtomicFile af = new AtomicFile(file); mDataBase.mHistoryFiles.addLast(af); } @@ -415,4 +418,36 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { assertThat(mDataBase.mBuffer).isNotEqualTo(nh); verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any()); } + + @Test + public void testRemoveFilePathFromHistory_hasMatch() throws Exception { + for (int i = 0; i < 5; i++) { + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i)); + mDataBase.mHistoryFiles.addLast(af); + } + // Baseline size of history files + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + + // Remove only file number 3 + String filePathToRemove = new File(mRootDir, "af3").getAbsolutePath(); + mDataBase.removeFilePathFromHistory(filePathToRemove); + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(4); + } + + @Test + public void testRemoveFilePathFromHistory_noMatch() throws Exception { + for (int i = 0; i < 5; i++) { + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i)); + mDataBase.mHistoryFiles.addLast(af); + } + // Baseline size of history files + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + + // Attempt to remove a filename that doesn't exist, expect nothing to break or change + String filePathToRemove = new File(mRootDir, "af.thisfileisfake").getAbsolutePath(); + mDataBase.removeFilePathFromHistory(filePathToRemove); + assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(5); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 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/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 7c2cfab50821..ee1d39328555 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -1515,6 +1515,129 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(primarySplitBounds, letterboxedBounds); } + @Test + public void testUpdateResolvedBoundsHorizontalPosition_left() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 0.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(0, 0, 350, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_center() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 0.5f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_invalidMultiplier_defaultToCenter() { + // Display configured as (2800, 1400). + + // Below 0.0. + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ -1.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + + // Above 1.0 + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 2.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(525, 0, 875, 700)); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_right() { + // Display configured as (2800, 1400). + assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + /* letterboxHorizontalPositionMultiplier */ 1.0f, + // At launch. + /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400), + // After 90 degree rotation. + /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400), + // After the display is resized to (700, 1400). + /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700)); + } + + private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity( + float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox, + Rect sizeCompatUnscaled, Rect sizeCompatScaled) { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + mActivity.mWmService.setLetterboxHorizontalPositionMultiplier( + letterboxHorizontalPositionMultiplier); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); + + assertEquals(fixedOrientationLetterbox, mActivity.getBounds()); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + assertTrue(mActivity.inSizeCompatMode()); + // Activity is in size compat mode but not scaled. + assertEquals(sizeCompatUnscaled, mActivity.getBounds()); + + // Force activity to scaled down for size compat mode. + resizeDisplay(mTask.mDisplayContent, 700, 1400); + + assertTrue(mActivity.inSizeCompatMode()); + assertScaled(); + assertEquals(sizeCompatScaled, mActivity.getBounds()); + } + + @Test + public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { + // When activity width equals parent width, multiplier shouldn't have any effect. + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 0.0f); + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 0.5f); + assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + /* letterboxHorizontalPositionMultiplier */ 1.0f); + } + + private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity( + float letterboxHorizontalPositionMultiplier) { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + mActivity.mWmService.setLetterboxHorizontalPositionMultiplier( + letterboxHorizontalPositionMultiplier); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + + assertFitted(); + + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + + assertTrue(mActivity.inSizeCompatMode()); + // Activity is in size compat mode but not scaled. + assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds()); + } + private static WindowState addWindowToActivity(ActivityRecord activity) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java index f3616da6c102..619aee6eb919 100644 --- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java +++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java @@ -17,6 +17,8 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.graphics.ColorSpace; +import android.graphics.GraphicBuffer; import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.Region; @@ -253,4 +255,14 @@ public class StubTransaction extends SurfaceControl.Transaction { public SurfaceControl.Transaction unsetFixedTransformHint(@NonNull SurfaceControl sc) { return this; } + + @Override + public SurfaceControl.Transaction setBuffer(SurfaceControl sc, GraphicBuffer buffer) { + return this; + } + + @Override + public SurfaceControl.Transaction setColorSpace(SurfaceControl sc, ColorSpace colorSpace) { + return this; + } } 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/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 5bafbbd2bdf7..ae12062987cd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -101,6 +101,7 @@ import android.window.TransitionRequestInfo; import com.android.internal.policy.AttributeCache; import com.android.internal.util.ArrayUtils; +import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.runner.Description; @@ -208,10 +209,25 @@ class WindowTestsBase extends SystemServiceTestsBase { // {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set // on some device form factors. mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio(0); + // Ensure letterbox position multiplier is not overridden on any device target. + // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}, + // may be set on some device form factors. + mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier(0.5f); checkDeviceSpecificOverridesNotApplied(); } + @After + public void tearDown() throws Exception { + // Revert back to device overrides. + mAtm.mWindowManager.setFixedOrientationLetterboxAspectRatio( + mContext.getResources().getFloat( + com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio)); + mAtm.mWindowManager.setLetterboxHorizontalPositionMultiplier( + mContext.getResources().getFloat( + com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier)); + } + /** * Check that device-specific overrides are not applied. Only need to check once during entire * test run for each case: global overrides, default display, and test display. diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 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/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java index 32533cbf97fb..0d6cd5a30e8c 100644 --- a/telephony/common/com/android/internal/telephony/SmsApplication.java +++ b/telephony/common/com/android/internal/telephony/SmsApplication.java @@ -760,6 +760,7 @@ public final class SmsApplication { private static void assignExclusiveSmsPermissionsToSystemApp(Context context, PackageManager packageManager, AppOpsManager appOps, String packageName, boolean sigatureMatch) { + if (packageName == null) return; // First check package signature matches the caller's package signature. // Since this class is only used internally by the system, this check makes sure // the package signature matches system signature. diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index c8ed82cd2a3f..0539897de33a 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1055,6 +1055,20 @@ public final class DataFailCause { */ public static final int HANDOVER_FAILED = 0x10006; + /** + * Enterprise setup failure: duplicate CID in DataCallResponse. + * + * @hide + */ + public static final int DUPLICATE_CID = 0x10007; + + /** + * Enterprise setup failure: no default data connection set up yet. + * + * @hide + */ + public static final int NO_DEFAULT_DATA = 0x10008; + private static final Map<Integer, String> sFailCauseMap; static { sFailCauseMap = new HashMap<>(); @@ -1481,6 +1495,9 @@ public final class DataFailCause { sFailCauseMap.put(UNACCEPTABLE_NETWORK_PARAMETER, "UNACCEPTABLE_NETWORK_PARAMETER"); sFailCauseMap.put(LOST_CONNECTION, "LOST_CONNECTION"); + sFailCauseMap.put(HANDOVER_FAILED, "HANDOVER_FAILED"); + sFailCauseMap.put(DUPLICATE_CID, "DUPLICATE_CID"); + sFailCauseMap.put(NO_DEFAULT_DATA, "NO_DEFAULT_DATA"); } private DataFailCause() { @@ -1580,6 +1597,7 @@ public final class DataFailCause { add(RADIO_NOT_AVAILABLE); add(UNACCEPTABLE_NETWORK_PARAMETER); add(SIGNAL_LOST); + add(DUPLICATE_CID); } }; } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f0771bedc838..c8e5e56031bc 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -4885,8 +4885,9 @@ public class TelephonyManager { * Return the set of IMSIs that should be considered "merged together" for data usage * purposes. This API merges IMSIs based on subscription grouping: IMSI of those in the same * group will all be returned. - * Return the current IMSI if there is no subscription group. See - * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group. + * Return the current IMSI if there is no subscription group, see + * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group, + * otherwise return an empty array if there is a failure. * * @hide */ diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java index ec859955694c..2d230a74a477 100644 --- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java +++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java @@ -23,6 +23,7 @@ import android.app.blob.BlobStoreManager; import android.app.blob.LeaseInfo; import android.content.Context; import android.content.res.Resources; +import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -56,6 +57,16 @@ public class Utils { } } + public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input) + throws IOException { + try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) { + try (FileOutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream( + session.openWrite(0, -1))) { + FileUtils.copy(in, out); + } + } + } + public static void writeToSession(BlobStoreManager.Session session, ParcelFileDescriptor input, long lengthBytes) throws IOException { try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(input)) { diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 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); diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java index da0571ba88e1..3b7566051fae 100644 --- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java +++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java @@ -117,13 +117,13 @@ public class WifiNl80211Manager { /** * Interface used to listen country code event */ - public interface CountryCodeChangeListener { + public interface CountryCodeChangedListener { /** * Called when country code changed. * - * @param countryCode A new country code which is 2-Character alphanumeric. + * @param countryCode An ISO-3166-alpha2 country code which is 2-Character alphanumeric. */ - void onChanged(@NonNull String countryCode); + void onCountryCodeChanged(@NonNull String countryCode); } /** @@ -163,27 +163,27 @@ public class WifiNl80211Manager { /** @hide */ @VisibleForTesting public class WificondEventHandler extends IWificondEventCallback.Stub { - private Map<CountryCodeChangeListener, Executor> mCountryCodeChangeListenerHolder = + private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder = new HashMap<>(); /** - * Register CountryCodeChangeListener with pid. + * Register CountryCodeChangedListener with pid. * * @param executor The Executor on which to execute the callbacks. * @param listener listener for country code changed events. */ - public void registerCountryCodeChangeListener(Executor executor, - CountryCodeChangeListener listener) { - mCountryCodeChangeListenerHolder.put(listener, executor); + public void registerCountryCodeChangedListener(Executor executor, + CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.put(listener, executor); } /** - * Unregister CountryCodeChangeListener with pid. + * Unregister CountryCodeChangedListener with pid. * * @param listener listener which registered country code changed events. */ - public void unregisterCountryCodeChangeListener(CountryCodeChangeListener listener) { - mCountryCodeChangeListenerHolder.remove(listener); + public void unregisterCountryCodeChangedListener(CountryCodeChangedListener listener) { + mCountryCodeChangedListenerHolder.remove(listener); } @Override @@ -191,8 +191,8 @@ public class WifiNl80211Manager { Log.d(TAG, "OnRegDomainChanged " + countryCode); final long token = Binder.clearCallingIdentity(); try { - mCountryCodeChangeListenerHolder.forEach((listener, executor) -> { - executor.execute(() -> listener.onChanged(countryCode)); + mCountryCodeChangedListenerHolder.forEach((listener, executor) -> { + executor.execute(() -> listener.onCountryCodeChanged(countryCode)); }); } finally { Binder.restoreCallingIdentity(token); @@ -1240,25 +1240,25 @@ public class WifiNl80211Manager { * @param listener listener for country code changed events. * @return true on success, false on failure. */ - public boolean registerCountryCodeChangeListener(@NonNull @CallbackExecutor Executor executor, - @NonNull CountryCodeChangeListener listener) { + public boolean registerCountryCodeChangedListener(@NonNull @CallbackExecutor Executor executor, + @NonNull CountryCodeChangedListener listener) { if (!retrieveWificondAndRegisterForDeath()) { return false; } Log.d(TAG, "registerCountryCodeEventListener called"); - mWificondEventHandler.registerCountryCodeChangeListener(executor, listener); + mWificondEventHandler.registerCountryCodeChangedListener(executor, listener); return true; } /** - * Unregister CountryCodeChangeListener with pid. + * Unregister CountryCodeChangedListener with pid. * * @param listener listener which registered country code changed events. */ - public void unregisterCountryCodeChangeListener(@NonNull CountryCodeChangeListener listener) { + public void unregisterCountryCodeChangedListener(@NonNull CountryCodeChangedListener listener) { Log.d(TAG, "unregisterCountryCodeEventListener called"); - mWificondEventHandler.unregisterCountryCodeChangeListener(listener); + mWificondEventHandler.unregisterCountryCodeChangedListener(listener); } /** diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java index 98a0042a7096..3fb23013bdec 100644 --- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java +++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java @@ -98,9 +98,9 @@ public class WifiNl80211ManagerTest { @Mock private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback; @Mock - private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener; + private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener; @Mock - private WifiNl80211Manager.CountryCodeChangeListener mCountryCodeChangeListener2; + private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2; @Mock private Context mContext; private TestLooper mLooper; @@ -768,25 +768,25 @@ public class WifiNl80211ManagerTest { } /** - * Ensures callback works after register CountryCodeChangeListener. + * Ensures callback works after register CountryCodeChangedListener. */ @Test - public void testCountryCodeChangeListenerInvocation() throws Exception { - assertTrue(mWificondControl.registerCountryCodeChangeListener( - Runnable::run, mCountryCodeChangeListener)); - assertTrue(mWificondControl.registerCountryCodeChangeListener( - Runnable::run, mCountryCodeChangeListener2)); + public void testCountryCodeChangedListenerInvocation() throws Exception { + assertTrue(mWificondControl.registerCountryCodeChangedListener( + Runnable::run, mCountryCodeChangedListener)); + assertTrue(mWificondControl.registerCountryCodeChangedListener( + Runnable::run, mCountryCodeChangedListener2)); mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener2).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener2).onCountryCodeChanged(TEST_COUNTRY_CODE); - reset(mCountryCodeChangeListener); - reset(mCountryCodeChangeListener2); - mWificondControl.unregisterCountryCodeChangeListener(mCountryCodeChangeListener2); + reset(mCountryCodeChangedListener); + reset(mCountryCodeChangedListener2); + mWificondControl.unregisterCountryCodeChangedListener(mCountryCodeChangedListener2); mWificondEventHandler.OnRegDomainChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener).onChanged(TEST_COUNTRY_CODE); - verify(mCountryCodeChangeListener2, never()).onChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener).onCountryCodeChanged(TEST_COUNTRY_CODE); + verify(mCountryCodeChangedListener2, never()).onCountryCodeChanged(TEST_COUNTRY_CODE); } /** |